-- UIGrid
-- create by zouyb
-- 格子

require "game/logic/clone/Grid"
require "game/ui/form/shop/UIDungeonShop"
require "game/ui/form/monster/UIMonsterInfo"
require "game/ui/form/summon/UISummonInfo"
require "game/ui/module/monster/MonsterDisplay"
require "game/ui/module/monster/ExtraMonsterDisplay"
require "game/ui/module/monster/AccompanyDisplay"
require "game/ui/module/summon/SummonDisplay"
require "game/ui/module/ship/ShipDisplay"
require "game/ui/form/fountain/UIDungeonFountain"
require "game/ui/form/dungeon/UIBuildingView"
require "game/ui/form/dungeon/UIBuildingView2"
require "game/ui/form/dungeon/UIBuildingView3"
require "game/ui/form/dungeon/UIBuildingView4"
require "game/ui/form/dungeon/UIBuildingView5"
require "game/ui/form/dungeon/UIBuildingSubmit"
require "game/ui/form/dungeon/UIInstanceEnter"
require "game/ui/form/dungeon/UIUnrealEnter"
require "game/ui/form/dungeon/UIDungeonSailor"
require "game/ui/form/monster/UIBossMonsterInfo"
require "game/ui/form/ghost_ship/UIShipMain"
require "game/ui/form/ghost_ship/UIShipSelectTarget"
require "game/ui/form/monster/UIShipMonsterInfo"
require "game/ui/form/guide/UIFeatureDesc"
require "game/ui/form/dungeon/special/UIStrongBox"
require "game/ui/form/dungeon/special/UIPeterMonkey"
require "game/ui/module/CommonActionManager"

-- 每行的格子数
local GRID_COL_NUM = 5;

-- 拾取到多种物品时飞入的时间间隔
local PICK_INTERVALE = 0.5;

-- UIGrid继承自Layer
UIGrid = class("UIGrid", function()
    return cc.Layer:create();
end);

function UIGrid.create(gridData, index, coverPath, decoratePath)
    return UIGrid.new(gridData, index, coverPath, decoratePath);
end

-- 构造函数
function UIGrid:ctor(gridData, index, coverPath, decoratePath)
    local node = cc.CSLoader:createNode("layout/dungeon/Grid.csb");
    self:addChild(node);
    self.node = node;

    self.index = index;
    self.gridData = gridData;
    self.coverPath = coverPath;

    self.lock = findChildByName(self.node, "lock");
    self.cover = findChildByName(self.node, "cover");
    self.shadow = findChildByName(self.node, "shadow");
    self.decorate = findChildByName(self.node, "decorate");
    self.poison = findChildByName(self.node, "poison");
    self.monsterNode = findChildByName(self.node, "monster");
    self.monsterBgNode = findChildByName(self.node, "monster_bg_node");

    self.barNode = findChildByName(self.node, "bar_node");
    self.hpBarNode = findChildByName(self.barNode, "hp_node");
    self.atkBarNode = findChildByName(self.barNode, "atk_node");
    self.hpLabel = findChildByName(self.barNode, "hp_node/hp");
    self.attackLabel = findChildByName(self.barNode, "atk_node/attack");

    self.item = findChildByName(self.node, "item");
    self.bineSkillNode = findChildByName(self.node, "bine_skill_node");
    self.talkNode = findChildByName(self.node, "talk_node");
    self.monsterMark = findChildByName(self.node, "monster_mark");
    self.wordsNode = findChildByName(self.node, "words_node");

    self.energy = findChildByName(self.node, "energy");

    --隐藏boss背景框
    local frameBg1 = findChildByName(self.monsterBgNode, "frame_bg1");
    frameBg1:setVisible(false);

    local frameBg2 = findChildByName(self.monsterBgNode, "frame_bg2");
    frameBg2:setVisible(false);

    -- 隐藏buff和debuff
    local buffNode = findChildByName(self.barNode, "buff_node");
    local debuffNode = findChildByName(self.barNode, "debuff_node");
    buffNode:setVisible(false);
    debuffNode:setVisible(false);

    -- 隐藏effect_cut_node和effect_node
    local altarEffectParent = findChildByName(self.node, "effect_cut_node");
    local dustEffectParent = findChildByName(self.node, "effect_node");
    altarEffectParent:setVisible(false);
    dustEffectParent:setVisible(false);

    -- 记录talkNode的原始横坐标
    self.talkNode.initPosX = self.talkNode:getPositionX();

    -- 打开格子的点击操作
    self.openGridClick = nil;

    -- 攻击怪物(或boss)的点击操作
    self.attackMonsterClick = nil;

    -- 拾取物品的点击操作
    self.pickItemClick = nil;

    -- 拾取宝箱的点击操作
    self.pickBoxClick = nil;

    -- 拾取冈布奥的点击操作
    self.pickSlimeClck = nil;

    -- 拾取冈布奥学徒的点击操作
    self.pickApprentice = nil;

    -- 拾取祭坛的点击操作
    self.pickAltarClick = 0;

    -- 拾取遗产的点击操作
    self.pickLegacyClick = nil;

    -- 拾取关卡地图的点击操作
    self.pickMapClick = nil;

    -- 拾取矿脉
    self.pickMineClick = 0;

    -- 进入地牢副本
    self.instanceClick = nil;

    -- 是否处于石化状态
    self.isInStone = false;

    -- 访问建筑记录
    self.visitBuildingRecord = {};

    if coverPath ~= "" then
        self.cover:loadTexture(coverPath, 1);
    end

    if decoratePath ~= "" then
        self.decorate:loadTexture(decoratePath, 1);
    else
        self.decorate:setVisible(false);
    end

    local dungeonId = DungeonM.getDungeonId();
    local style = DungeonAreaM.query(dungeonId, "style");
    self.shadow:loadTexture("images/cover/" .. style .. "/yingkuang.png", 1);

    self:gotoInvisible();

    -- 是否能进入下一关
    self.canEnterNextLevel = false;

    self.hasItem = false;
    self.hasMonster = false;

    -- 根据类型初始化格子
    self:initByType();

    -- 标记
    self:showMonsterMark(true);

    -- 格子动作播放管理器, 以一个不可见节点存在
    self.actionManager = CommonActionManager.create("grid"..tostring(index));
    self:addChild(self.actionManager);
end

-- 更新格子数据
function UIGrid:updateData(gridData)
    self.gridData = gridData;

    self:initByType();
end

-- 根据类型初始化格子
function UIGrid:initByType()
    if not self.gridData then
        return;
    end

    local type = self.gridData.type;

    if type == GRID_TYPE_DOOR then
        -- 门
        self:initDoor();
    elseif type == GRID_TYPE_BLOCK then
        -- 锁
        self:initBlock();
    elseif type == GRID_TYPE_EMPTY then
        -- 空格
        self:initEmpty();
    elseif type == GRID_TYPE_MONSTER then
        -- 怪物
        self:initMonster(true);
    elseif type == GRID_TYPE_ITEM then
        -- 物品
        self:initItem();
    elseif type == GRID_TYPE_ALTAR then
        -- 祭坛
        self:initAltar();
    elseif type == GRID_TYPE_SHOP then
        -- 商店
        require "game/ui/form/dungeon/grid/Shop";
        initShop(self);
    elseif type == GRID_TYPE_BANK then
        -- 银行
        --self:initBank();
        self:initEmpty();
    elseif type == GRID_TYPE_LEGACY then
        -- 遗产
        require "game/ui/form/dungeon/grid/Legacy";
        initLegacy(self);
    elseif type == GRID_TYPE_SLIME then
        -- 冈布奥
        self:initSlime();
    elseif type == GRID_TYPE_APPRENTICE then
        -- 冈布奥学徒
        self:initApprentice();
    elseif type == GRID_TYPE_MAP then
        -- 地图
        self:initMap();
    elseif type == GRID_TYPE_BOX then
        -- 宝箱
        self:initBox();
    elseif type == GRID_TYPE_FOUNTAIN then
        -- 幸运喷泉
        self:initFountain();
    elseif type == GRID_TYPE_MINE then
        -- 矿脉
        self:initMine();
    elseif type == GRID_TYPE_BLACKSMITH then
        -- 工坊
        require "game/ui/form/dungeon/grid/Blacksmith";
        initBlacksmith(self);
    elseif type == GRID_TYPE_INSTANCE then
        -- 地牢副本
        require "game/ui/form/dungeon/grid/Instance"
        initInstance(self);
    elseif type == GRID_TYPE_CAVE_BOX then
        -- 怪物巢穴副本宝箱
        self:initCaveBox();
    elseif type == GRID_TYPE_BOSS then
        -- 地牢BOSS
        self:initBoss();
    elseif type == GRID_TYPE_BOSS_BOX then
        -- BOSS宝箱
        self:initBossBox();
    elseif type == GRID_TYPE_ACTIVITY_BOSS_BOX then
        -- 活动BOSS宝箱
        self:initBossBox();
    elseif type == GRID_TYPE_BEEHIVE then
        -- 蜂巢
        require "game/ui/form/dungeon/grid/Beehive";
        initBeehive(self);
    elseif type == GRID_TYPE_TRAP then
        -- 陷阱
        require "game/ui/form/dungeon/grid/Trap";
        initTrap(self);
    elseif type == GRID_TYPE_FRUIT then
        -- 果树
        require "game/ui/form/dungeon/grid/Fruit";
        initFruit(self);
    elseif type == GRID_TYPE_WRECKAGE then
        -- 冒险者残骸
        require "game/ui/form/dungeon/grid/Wreckage";
        initWreckage(self);
    elseif type == GRID_TYPE_MARMITE then
        -- 砂锅
        require "game/ui/form/dungeon/grid/Marmite";
        initMarmite(self);
    elseif type == GRID_TYPE_STORAGE_BOX then
        -- 储物箱
        require "game/ui/form/dungeon/grid/StorageBox";
        initStorageBox(self);
    elseif type == GRID_TYPE_GAMBLE then
        -- 赌桌
        require "game/ui/form/dungeon/grid/Gamble";
        initGamble(self);
    elseif type == GRID_TYPE_RUBBISH then
        -- 垃圾堆
        self:initRubbish();
    elseif type == GRID_TYPE_STATUE then
        -- 雕像
        self:initStatue();
    elseif type == GRID_TYPE_JACK_CORPSE then
        -- 杰克的尸体
        self:initJackCorpse();
    elseif type == GRID_TYPE_QUEEN_CORPSE then
        -- 皇后的尸体
        self:initQueenCorpse();
    elseif type == GRID_TYPE_GARDEN then
        -- 花园
        require "game/ui/form/dungeon/grid/Garden";
        initGarden(self);
    elseif type == GRID_TYPE_SHIELD then
        -- 奇怪的盾牌
        self:initShield();
    elseif type == GRID_TYPE_VILLAGER then
        -- 村民
        self:initVillager();
    elseif type == GRID_TYPE_VILLAGER_FIGHTER then
        -- 斗士村民
        self:initVillager();
    elseif type == GRID_TYPE_TREASURE then
        -- 海贼的宝藏
        self:initTreasure()
    elseif type == GRID_TYPE_SKELETON then
        -- 骸骨堆
        self:initSkeleton();
    elseif type == GRID_TYPE_HEAD_ALTAR then
        -- 头骨祭坛
        self:initHeadAltar();
    elseif type == GRID_TYPE_LAB then
        -- 死灵实验台
        require "game/ui/form/dungeon/grid/Lab"
        initLab(self);
    elseif type == GRID_TYPE_SUMMON then
        -- 召唤兽
        self:initSummon();
    elseif type == GRID_TYPE_WOMAN_WRECKAGE then
        -- 女人的残骸
        self:initWomanWrecage();
    elseif type == GRID_TYPE_SHRINE then
        -- 神龛
        self:initShrine();
    elseif type == GRID_TYPE_ZORO_CORPSE then
        -- 佐罗的尸体
        self:initZoro();
    elseif type == GRID_TYPE_TIED_PIG then
        -- 被绑的野猪
        require "game/ui/form/dungeon/grid/TiedPig"
        initTiedPig(self);
    elseif type == GRID_TYPE_PILGRIM then
        -- 朝圣者残骸
        self:initPilgrim();
    elseif type == GRID_TYPE_ELEMENT_ALTAR or type == GRID_TYPE_ELEMENT_LORD then
        -- 元素圣坛
        require "game/ui/form/dungeon/grid/ElemAltar"
        initElemAltar(self);
    elseif type == GRID_TYPE_SPECIAL_ELEMENT_LORD then
        -- 特殊元素领主圣坛
        require "game/ui/form/dungeon/grid/SpecialElemLord"
        initSpecialElemLord(self);
    elseif type == GRID_TYPE_CAISSON then
        -- 弹药箱
        self:initCaisson();
    elseif type == GRID_TYPE_SKUMMER then
        -- 走私者残骸
        self:initSkummer();
    elseif type == GRID_TYPE_DRIFT_BOTTLE then
        -- 漂流瓶
        self:initDriftBottle();
    elseif type == GRID_TYPE_ELEM_MINE then
        -- 元素矿脉
        self:initElemMine();
    elseif type == GRID_TYPE_STONE then
        -- 石柱
        require "game/ui/form/dungeon/grid/Stone"
        initStone(self);
    elseif type == GRID_TYPE_WINNER_STONE then
        -- 胜利者石柱
        self:initWinnerStone();
    elseif type == GRID_TYPE_CABIN then
        -- 魅魔小屋
        self:initcabin();
    elseif type == GRID_TYPE_BOARD then
        -- 木牌
        self:initBoard();
    elseif type == GRID_TYPE_UNREAL_OBJECT then
        -- 神龙夹层普通物件
        self:initUnrealObject();
    elseif type == GRID_TYPE_UNREAL_SPECIAL then
        -- 神龙夹层特殊物件
        self:initUnrealSpecial();
    elseif type == GRID_TYPE_SAILOR_WRECKAGE then
        -- 水手的残骸
        self:initSailorWreckage();
    elseif type == GRID_TYPE_SHIPWRECK then
        -- 沉船
        self:initShipWreck();
    elseif type == GRID_TYPE_GHOST_SHIP then
        -- 幽灵船
        self:initGhostShip();
    elseif type == GRID_TYPE_MINE_HILL then
        -- 矿山
        self:initMineHill();
    elseif type == GRID_TYPE_BELIEVER then
        -- 信徒
        self:initBeliever();
    elseif type == GRID_TYPE_FEATURE_ITEM then
        -- 功能引导道具
        self:initFeatureItem();
    elseif type == GRID_TYPE_DEVELOPER_BOARD then
        -- 开发组木牌
        require "game/ui/form/dungeon/grid/DeveloperBoard"
        initDeveloperBoard(self);
    elseif type == GRID_TYPE_ODD_ALTAR then
        -- 奇怪的祭坛
        require "game/ui/form/dungeon/grid/OddAltar"
        initOddAltar(self);
    elseif type == GRID_TYPE_CRAVEN_SKELETON then
        -- 胆小的骷髅
        require "game/ui/form/dungeon/grid/CravenSkeleton"
        initCravenSkeleton(self);
    elseif type == GRID_TYPE_RUSTY_BOW then
        -- 生锈的重弩
        require "game/ui/form/dungeon/grid/RustyBow"
        initRustyBow(self);
    elseif type == GRID_TYPE_PIXY_WELL then
        -- 妖精之井
        require "game/ui/form/dungeon/grid/PixyWell"
        initPixyWell(self);
    elseif type == GRID_TYPE_DESK then
        -- 木桌
        self:initDesk();
    elseif type == GRID_TYPE_STONE_BOX or type == GRID_TYPE_HIDE_STRONGBOX then
        -- 隐蔽的石匣
        require "game/ui/form/dungeon/grid/StoneBox"
        initStoneBox(self);
    elseif type == GRID_TYPE_PIXY_CAGE then
        -- 妖精的铁笼
        require "game/ui/form/dungeon/grid/PixyCage"
        initPixyCage(self);
    elseif type == GRID_TYPE_PIXY then
        -- 小妖精
        require "game/ui/form/dungeon/grid/Pixy"
        initPixy(self);
    elseif type == GRID_TYPE_ODD_FLOOR then
        -- 奇怪的地板
        require "game/ui/form/dungeon/grid/OddFloor"
        initOddFloor(self);
    elseif type == GRID_TYPE_CRYSTAL_BALL then
        -- 水晶球
        self:initCrystalBall();
    elseif type == GRID_TYPE_UNREAL_DUNGEON then
        -- 夹层迷宫
        require "game/ui/form/dungeon/grid/UnrealDungeon"
        initUnrealDungeon(self);
    elseif type == GRID_TYPE_MISTIC_CAVE then
        -- 神秘山洞
        require "game/ui/form/dungeon/grid/MisticCave";
        initMisticCave(self);
    elseif type == GRID_TYPE_SPHINX then
        -- 狮身人面像
        require "game/ui/form/dungeon/grid/Sphinx";
        initSphinx(self);
    elseif type == GRID_TYPE_DESERT_ALTAR then
        -- 沙漠祭坛
        require "game/ui/form/dungeon/grid/DesertAltar";
        initDesertAltar(self);
    elseif type == GRID_TYPE_BOOKSHELF then
        -- 废弃的书架
        require "game/ui/form/dungeon/grid/Bookshelf";
        initBookshelf(self);
    elseif type == GRID_TYPE_CRYSTAL_BALL then
        -- 水晶球
        self:initCrystalBall();
    elseif type == GRID_TYPE_OLD_TV then
        -- 老式电视机
        self:initOldTv();
    elseif type == GRID_TYPE_TOPLIST then
        -- 排行榜建筑
        require "game/ui/form/dungeon/grid/Toplist";
        initToplist(self);
    elseif type == GRID_TYPE_DEMON_STATUE then
        -- 魔神雕像
        require "game/ui/form/dungeon/grid/DemonStatue";
        initDemonStatue(self);
    elseif type == GRID_TYPE_SHOWCASE then
        -- 德古拉的展柜
        require "game/ui/form/dungeon/grid/Showcase";
        initShowcase(self);
    elseif type == GRID_TYPE_SLIME_EVENT then
        -- 冈布奥触发事件
        require "game/ui/form/dungeon/grid/SlimeEvent";
        initSlimeEvent(self);
    elseif type == GRID_TYPE_BLOOD_ALTAR then
        -- 鲜血祭坛
        require "game/ui/form/dungeon/grid/BloodAltar";
        initBloodAltar(self);
    elseif type == GRID_TYPE_COFFIN then
        -- 圣棺
        require "game/ui/form/dungeon/grid/Coffin";
        initCoffin(self);
    elseif type == GRID_TYPE_ARENA then
        -- 角斗台
        require "game/ui/form/dungeon/grid/Arena";
        initArena(self);
    elseif type == GRID_TYPE_ARENA_BOX then
        -- 角斗台储物箱
        require "game/ui/form/dungeon/grid/ArenaBox";
        initArenaBox(self);
    elseif type == GRID_TYPE_ACTIVITY_BOX then
        -- 活动副本储物箱
        require "game/ui/form/dungeon/grid/ActivityBox";
        initActivityBox(self);
    elseif type == GRID_TYPE_ARENA_BOARD then
        -- 角斗台木牌
        require "game/ui/form/dungeon/grid/ArenaBoard";
        initArenaBoard(self);
    elseif type == GRID_TYPE_ARENA_FLAG then
        -- 角斗台白旗
        require "game/ui/form/dungeon/grid/ArenaFlag";
        initArenaFlag(self);
    elseif type == GRID_TYPE_CREATION_GATE then
        -- 创世之门
        require "game/ui/form/dungeon/grid/CreationGate";
        initCreationGate(self);
    elseif type == GRID_TYPE_CREATION_GATE2 then
        -- 创世之门
        require "game/ui/form/dungeon/grid/CreationGate2";
        initCreationGate2(self);
    elseif type == GRID_TYPE_DARK_DRAGON then
        -- 黑暗龙雕像
        require "game/ui/form/dungeon/grid/DarkDragon";
        initDarkDragon(self);
    elseif type == GRID_TYPE_SHADOW_SPACE then
        -- 阴影空间（阴影雕像）
        require "game/ui/form/dungeon/grid/ShadowSpace";
        initShadowSpace(self);
    elseif type == GRID_TYPE_AIOULIYA_STATUE then
        -- 艾欧利亚雕像
        require "game/ui/form/dungeon/grid/AiouliyaStatue";
        initAiouliyaStatue(self);
    elseif type == GRID_TYPE_OLD_STRONGBOX then
        -- 古老的保险箱
        require "game/ui/form/dungeon/grid/OldStrongbox";
        initOldStrongbox(self);
    elseif type == GRID_TYPE_EAST_SHRINE then
        -- 东方的神龛
        require "game/ui/form/dungeon/grid/EastShine";
        initEastShine(self);
    elseif type == GRID_TYPE_MACHINE_ALTAR then
        -- 机械祭坛
        require "game/ui/form/dungeon/grid/MachineAltar";
        initMachineAltar(self);
    elseif type == GRID_TYPE_NORMAL_SUBMIT then
        -- 通用提交建筑
        require "game/ui/form/dungeon/grid/NormalSubmit"
        initNormalSubmit(self);
    elseif type == GRID_TYPE_SKY_OBJECT then
        -- 天空副本通用建筑
        require "game/ui/form/dungeon/grid/SkyObject";
        initSkyObject(self);
    elseif type == GRID_TYPE_TRANSFORM_DEVICE then
        -- 改造装置
        require "game/ui/form/dungeon/grid/TransformDevice";
        initTransformDevice(self);
    elseif type == GRID_TYPE_DIAMON_STATUE or type == GRID_TYPE_GOLD_CAN then
        -- 钻石雕像、黄金罐子（其实就是击杀boss掉落物品）
        require "game/ui/form/dungeon/grid/bossDropItem";
        initBossDropItem(self);
    elseif type == GRID_TYPE_DRAGON_SHADOW then
        -- 神龙虚影
        require "game/ui/form/dungeon/grid/DragonShadow";
        initDragonShadow(self);
    elseif type == GRID_TYPE_SPECIAL_BUILDING then
        require "game/ui/form/dungeon/grid/SpecialBuilding";
        initSpecialBuilding(self);
    elseif type == GRID_TYPE_SKY_LAB then
        -- 死灵实验台
        require "game/ui/form/dungeon/grid/SkyLab";
        initSkyLab(self);
    elseif type == GRID_TYPE_TREE_HOUSE then
        -- 树屋
        require "game/ui/form/dungeon/grid/TreeHouse";
        initTreeHouse(self);
    elseif type == GRID_TYPE_DISPLAY_OBJECT then
        -- 用来显示的图标，不可操作
        require "game/ui/form/dungeon/grid/DisplayObject";
        initDisplayObject(self);
    elseif type == GRID_TYPE_PALADIN_EXPERIENCE then
        -- 圣骑士的心得
        require "game/ui/form/dungeon/grid/PaladinExperience";
        initPaladinExperience(self);
    elseif type == GRID_TYPE_PALADIN_FOYER then
        require "game/ui/form/dungeon/grid/PaladinFoyer";
        initPaladinFoyer(self);
    elseif type == GRID_TYPE_ALTAR_RUINS then
        require "game/ui/form/dungeon/grid/AltarRuins";
        initAltarRuins(self);
    elseif type == GRID_TYPE_KRYPTONITE then
        -- 氪石
        require "game/ui/form/dungeon/grid/Kryptonite"
        initKryptonite(self);
    elseif type == GRID_TYPE_JUSTICE then
        -- 正义使者
        require "game/ui/form/dungeon/grid/Justice";
        initJustice(self);
    elseif type == GRID_TYPE_INSECT_QUEEN then
        -- 虫族女王
        require "game/ui/form/dungeon/grid/InsectQueen";
        initInsectQueen(self);
    elseif type == GRID_TYPE_UNREAL_STATUE then
        -- 你的雕像
        require "game/ui/form/dungeon/grid/UnrealStatue";
        initUnrealStatue(self);
    elseif type == GRID_TYPE_NORMAL_OBJECT then
        -- 通用建筑对象
        require "game/ui/form/dungeon/grid/NormalObject";
        initNormalObject(self)
    elseif type == GRID_TYPE_MASTER_OOGWAY then
        -- 乌龟大师
        require "game/ui/form/dungeon/grid/MasterOogway";
        initMasterOogway(self);
    elseif type == GRID_TYPE_CAVE_HIDE_ITEM then
        -- 洞窟隐藏物件
        require "game/ui/form/dungeon/grid/CaveHideItem";
        initCaveHideItem(self);
    elseif type == GRID_TYPE_CRYSTAL_BARRIAL then
        -- 水晶结界
        require "game/ui/form/dungeon/grid/CrystalBar";
        initCrystalBar(self);
    elseif type == GRID_TYPE_RECRUIT_PET then
        -- 拯救冈布奥
        require "game/ui/form/dungeon/grid/RecruitPet";
        initRecruitPet(self);
    elseif type == GRID_TYPE_COMMANDER_SUMMON then
        -- 指挥官建筑
        require "game/ui/form/dungeon/grid/CommanderSummon";
        initCommanderSummon(self);
    elseif type == GRID_TYPE_VITAMIN_CABIN then
        -- 维生舱
        require "game/ui/form/dungeon/grid/VitaminCabin";
        initVitaminCabin(self);
    elseif type == GRID_TYPE_SPECIAL_HIDE_ITEM then
        -- 破旧的终端机
        require "game/ui/form/dungeon/grid/OldTerminal";
        initOldTerminal(self);
    elseif type == GRID_TYPE_ANU_STATUE then
        -- 阿努比斯的雕塑
        require "game/ui/form/dungeon/grid/AnuStatue";
        initAnuStatue(self);
    elseif type == GRID_TYPE_SKY_ELE_ALTAR then
        -- 元素圣坛
        require "game/ui/form/dungeon/grid/ElemAltar"
        initElemAltar(self);
    elseif type == GRID_TYPE_OLD_ANVIL then
        -- 老旧铁砧
        require "game/ui/form/dungeon/grid/OldAnvil"
        initOldAnvil(self);
    elseif type == GRID_TYPE_OUT_BONFIRE then
        -- 熄灭的篝火堆
        require "game/ui/form/dungeon/grid/OutBonfire"
        initOutBonfire(self);
    elseif type == GRID_TYPE_SKY_ELEM_MINE then
        -- 天空副本元素矿脉
        require "game/ui/form/dungeon/grid/SkyElemMine"
        initSkyElemMine(self);
    elseif type == GRID_TYPE_SKY_STRONGBOX then
        -- 天空副本的保险箱
        require "game/ui/form/dungeon/grid/SkyStrongBox"
        intSkyStrongBox(self);
    elseif type == GRID_TYPE_MONSTER_BUILDING then
        -- 刷怪建筑
        require "game/ui/form/dungeon/grid/MonsterBuilding"
        initMonsterBuilding(self);
    elseif type == GRID_TYPE_MACHINE_PLATFORM then
        -- 战斗平台
        require "game/ui/form/dungeon/grid/MachinePlatform"
        initMachinePlatform(self);
    elseif type == GRID_TYPE_PICTURE then
        -- 贴图格子
        require "game/ui/form/dungeon/grid/Picture"
        initPicture(self);
    elseif type == GRID_TYPE_DESK_CLOCK then
        -- 座钟
        require "game/ui/form/dungeon/grid/DeskClock"
        initDeskClock(self);
    elseif type == GRID_TYPE_BLOODY_TESTBED then
        -- 血腥试验台
        require "game/ui/form/dungeon/grid/BloodyTestbed"
        initBloodyTestbed(self);
    elseif type == GRID_TYPE_GUARD_STATUE then
        -- 守卫雕像
        require "game/ui/form/dungeon/grid/GuardStatue"
        initGuardStatue(self);
    elseif type == GRID_TYPE_SEAL_ROOM then
        -- 封印的房间
        require "game/ui/form/dungeon/grid/SealRoom"
        initSealRoom(self);
    elseif type == GRID_TYPE_SKY_BLOODYCAVE then
        -- 封印的房间
        require "game/ui/form/dungeon/grid/SkyBloodyCave"
        initSkyBloodyCave(self);
    elseif type == GRID_TYPE_CHESS_TABLE then
        -- 棋桌
        require "game/ui/form/dungeon/grid/ChessTable"
        initChessTable(self);
    elseif type == GRID_TYPE_GUARDS then
        -- 侍卫
        require "game/ui/form/dungeon/grid/Guards"
        initGuards(self);
    elseif type == GRID_TYPE_CHANCELLOR then
        -- 大臣
        require "game/ui/form/dungeon/grid/Chancellor"
        initChancellor(self);
    elseif type == GRID_TYPE_PRINCESS then
        -- 公主
        require "game/ui/form/dungeon/grid/Princess"
        initPrincess(self);
    elseif type == GRID_TYPE_KING then
        -- 国王
        require "game/ui/form/dungeon/grid/King"
        initKing(self);
    elseif type == GRID_TYPE_BRAVE_CAVE then
        -- 勇者洞窟
        require "game/ui/form/dungeon/grid/BraveCave"
        initBraveCave(self);
    elseif type == GRID_TYPE_CLOUDS_SHRINE then
        -- 云雨祠堂
        require "game/ui/form/dungeon/grid/CloudsShrine"
        initCloudsShrine(self);
    elseif type == GRID_TYPE_BRAVE_ITEM then
        -- 勇者斗恶龙中的通用物品
        require "game/ui/form/dungeon/grid/BraveItem"
        initBraveItem(self);
    elseif type == GRID_TYPE_BRAVE_SUBMITTER then
        -- 勇者斗恶龙提交道具NPC
        require "game/ui/form/dungeon/grid/BraveSubmitter"
        initBraveSubmitter(self);
    elseif type == GRID_TYPE_DRUNK then
        -- 醉汉
        require "game/ui/form/dungeon/grid/Drunk"
        initDrunk(self);
    elseif type == GRID_TYPE_DRUNK_BOX then
        -- 醉汉储物箱
        require "game/ui/form/dungeon/grid/DrunkBox"
        initDrunkBox(self);
    elseif type == GRID_TYPE_PIXY_SPRING then
        -- 妖精之泉
        require "game/ui/form/dungeon/grid/PixySpring"
        initPixySpring(self);
    elseif type == GRID_TYPE_PIXY_WELL_BRAVE then
        -- 妖精之井（勇者斗恶龙）
        require "game/ui/form/dungeon/grid/PixyWellBrave"
        initPixyWellBrave(self);
    elseif type == GRID_TYPE_EXCHANGER then
        -- 兑换建筑
        require "game/ui/form/dungeon/grid/Exchanger"
        initExchanger(self);
    elseif type == GRID_TYPE_POET then
        -- 吟游诗人
        require "game/ui/form/dungeon/grid/Poet"
        initPoet(self);
    elseif type == GRID_TYPE_GUARDS_LEADER then
        -- 侍卫队长
        require "game/ui/form/dungeon/grid/GuardsLeader"
        initGuardsLeader(self);
    elseif type == GRID_TYPE_COMMENT then
        -- Peter的木桌
        require "game/ui/form/dungeon/grid/Comment"
        initComment(self);
    elseif type == GRID_TYPE_BUSSNESSMAN then
        -- 商人
        require "game/ui/form/dungeon/grid/Bussinessman"
        initBussinessman(self);
    elseif type == GRID_TYPE_LITTLE_BOY then
        -- 小男孩
        require "game/ui/form/dungeon/grid/LittleBoy"
        initLittleBoy(self);
    elseif type == GRID_TYPE_LAIJIA_GRAVE then
        -- 莱迦之墓
        require "game/ui/form/dungeon/grid/LaijiaGrave"
        initLaijiaGrave(self);
    elseif type == GRID_TYPE_ADVENTURER then
        -- 冒险者
        require "game/ui/form/dungeon/grid/Adventurer"
        initAdventurer(self);
    elseif type == GRID_TYPE_BRAVE_CERTIFICATE then
        -- 勇者之证
        require "game/ui/form/dungeon/grid/BraveCertificate"
        initBraveCertificate(self);
    elseif type == GRID_TYPE_TRAVELLER_TENT then
        -- 旅者帐篷
        require "game/ui/form/dungeon/grid/TravellerTent"
        initTravellerTent(self);
    elseif type == GRID_TYPE_PRIEST then
        -- 神官
        require "game/ui/form/dungeon/grid/Priest"
        initPriest(self);
    elseif type == GRID_TYPE_HUGE_HOLE then
        -- 巨坑
        require "game/ui/form/dungeon/grid/HugeHole"
        initHugeHole(self);
    elseif type == GRID_TYPE_GRAVE then
        -- 坟墓
        require "game/ui/form/dungeon/grid/Grave"
        initGrave(self);
    elseif type == GRID_TYPE_HOLY_SHRINE then
        -- 神圣祠堂
        require "game/ui/form/dungeon/grid/HolyShrine"
        initHolyShrine(self);
    elseif type == GRID_TYPE_DRAGON_KING then
        -- 龙王
        require "game/ui/form/dungeon/grid/DragonKing"
        initDragonKing(self);
    elseif type == GRID_TYPE_DRAGON_KING_BOX then
        -- 龙王宝箱
        require "game/ui/form/dungeon/grid/DragonKingBox"
        initDragonKingBox(self);
    elseif type == GRID_TYPE_TRANS_ELIMINATE then
        -- 转换消除
        require "game/ui/form/dungeon/grid/TransEliminate"
        initTransEliminate(self);
    elseif type == GRID_TYPE_DARK_STONE then
        -- 暗之石柱
        require "game/ui/form/dungeon/grid/DarkStone"
        initDarkStone(self);
    elseif type == GRID_TYPE_WAKUSEI then
        -- 变异体冈布奥
        require "game/ui/form/dungeon/grid/Wakuse"
        initWakuse(self);
    elseif type == GRID_TYPE_BOSS_STONE then
        -- boss层光/暗石柱
        require "game/ui/form/dungeon/grid/BossStone"
        initBossStone(self);
    elseif type == GRID_TYPE_REBELS then
        -- 受伤的反叛军
        require "game/ui/form/dungeon/grid/Rebels"
        initRebels(self);
    elseif type == GRID_TYPE_BISHOP_PRE then
        -- 主教塞缪尔建筑
        require "game/ui/form/dungeon/grid/BishopPre"
        initBishopPre(self);
    elseif type == GRID_TYPE_BISHOP then
        -- 主教塞缪尔
        require "game/ui/form/dungeon/grid/Bishop"
        initBishop(self);
    elseif type == GRID_TYPE_CG_UNREAL_OBJ then
        -- 创世之门夹层建筑
        require "game/ui/form/dungeon/grid/CGUnrealObj"
        initCGUnrealObj(self);
    elseif type == GRID_TYPE_BREAD_MILL then
        -- 面包磨坊
        require "game/ui/form/dungeon/grid/BreadMill"
        initBreadMill(self);
    elseif type == GRID_TYPE_STONE_STAGE  then
        -- 石台
        require "game/ui/form/dungeon/grid/StoneStage"
        initStoneStage(self);
    elseif type == GRID_TYPE_EVIL_STATUE then
        -- 雕像
        require "game/ui/form/dungeon/grid/EvilStatue"
        initEvilStatue(self);
    elseif type == GRID_TYPE_MAGIC_MIRROR then
        -- 魔镜
        require "game/ui/form/dungeon/grid/MagicMirror"
        initMagicMirror(self);
    elseif type == GRID_TYPE_LOCKED_SLIME then
        -- 被关押的冈布奥
        require "game/ui/form/dungeon/grid/LockedSlime"
        initLockedSlime(self);
    elseif type == GRID_TYPE_GODFATHER_STORAGE then
        -- 宇宙教父的储藏器
        require "game/ui/form/dungeon/grid/GodfatherStorage"
        initGodfatherStorage(self);
    elseif type == GRID_TYPE_AVALON_CANNON then
        -- 阿瓦隆巨炮
        require "game/ui/form/dungeon/grid/AvalonCannon"
        initAvalonCannon(self);
    elseif type == GRID_TYPE_AIRSHIP_CONSOLE then
        -- 飞艇控制台
        require "game/ui/form/dungeon/grid/AirshipConsole"
        initAirshipConsole(self);
    elseif type == GRID_TYPE_BS_POLICE then
        -- 蓝鲨警官
        require "game/ui/form/dungeon/grid/BSPolice"
        initBSPolice(self);
    elseif type == GRID_TYPE_OPERATE_TABLE then
        -- 操作台
        require "game/ui/form/dungeon/grid/OperateTable"
        initOperateTable(self);
    elseif type == GRID_TYPE_ENERGY_PLATFORM then
        -- 超导充能平台
        require "game/ui/form/dungeon/grid/EnergyPlatform"
        initEnergyPlatform(self);
    elseif type == GRID_TYPE_AVALON_LAB then
        -- 阿瓦隆要塞实验室
        require "game/ui/form/dungeon/grid/AvalonLab"
        initAvalonLab(self);
    elseif type == GRID_TYPE_TIMEBOMB then
        -- 定时炸弹
        require "game/ui/form/dungeon/grid/Timebomb"
        initTimebomb(self);
    elseif type == GRID_TYPE_GAS_DEVICE then
        -- 毒气装置
        require "game/ui/form/dungeon/grid/GasDevice"
        initGasDevice(self);
    elseif type == GRID_TYPE_STEEL_DOOR then
        -- 钢铁之门
        require "game/ui/form/dungeon/grid/SteelDoor"
        initSteelDoor(self);
    elseif type == GRID_TYPE_INTELLIGENCE_PC then
        -- 智能电脑
        require "game/ui/form/dungeon/grid/IntelligencePC"
        initIntelligencePC(self);
    elseif type == GRID_TYPE_BROKEN_PC then
        -- 破旧的电脑
        require "game/ui/form/dungeon/grid/BrokenPC"
        initBrokenPC(self);
    elseif type == GRID_TYPE_INTERFERE_DEVICE then
        -- 电流干扰装置
        require "game/ui/form/dungeon/grid/InterfereDevice"
        initInterfereDevice(self);
    elseif type == GRID_TYPE_STEAM_MACHINE then
        -- 蒸汽机械
        require "game/ui/form/dungeon/grid/SteamMachine"
        initSteamMachine(self);
    elseif type == GRID_TYPE_IRON_CHEST then
        -- 铁柜
        require "game/ui/form/dungeon/grid/IronChest"
        initIronChest(self);
    elseif type == GRID_TYPE_CHAIR then
        -- 电椅
        require "game/ui/form/dungeon/grid/Chair"
        initChair(self);
    elseif type == GRID_TYPE_TV then
        -- 电视机
        require "game/ui/form/dungeon/grid/Televation"
        initTelevation(self);
    elseif type == GRID_TYPE_NORMAL_GAMBLE then
        -- 通用赌桌
        require "game/ui/form/dungeon/grid/Billiards"
        initBilliards(self);
    elseif type == GRID_TYPE_GUARD_CHEST then
        -- 警卫室铁柜
        require "game/ui/form/dungeon/grid/GuardChest"
        initGuardChest(self);
    elseif type == GRID_TYPE_CHRISTMAS_HOUSE then
        -- 圣诞小屋
        require "game/ui/form/dungeon/grid/ChristmasHouse"
        initChristmasHouse(self);
    elseif type == GRID_TYPE_MASSAGE_ROOM then
        -- 按摩房
        require "game/ui/form/dungeon/grid/MassageRoom"
        initMassageRoom(self);
    elseif type == GRID_TYPE_BLACK_MARKET then
        -- 黑市摊贩
        require "game/ui/form/dungeon/grid/BlackMarket"
        initBlackMarket(self);
    elseif type == GRID_TYPE_FAIRY then
        -- 仙女
        require "game/ui/form/dungeon/grid/PinocchioFairy"
        initPinocchioFairy(self);
    elseif type ==  GRID_TYPE_STRANGE_PC then
        -- 奇怪的电脑
        require "game/ui/form/dungeon/grid/StrangePC"
        initStrangePC(self);
    elseif type == GRID_TYPE_BROKEN_ROBOT then
        require "game/ui/form/dungeon/grid/brokenRobot";
        initBrokenRobot(self);
    elseif type == GRID_TYPE_BONUS_BUILDING then
        -- 奖励建筑
        require "game/ui/form/dungeon/grid/BonusBuilding"
        initBonusBuilding(self);
    elseif type == GRID_TYPE_SUMMON_BLACKSMITH then
        -- 召唤兽工坊
        require "game/ui/form/dungeon/grid/SummonBlacksmith"
        initSummonBlacksmith(self);
    elseif type == GRID_TYPE_CREATION_DOOR then
        -- 创世之门夹层的门
        require "game/ui/form/dungeon/grid/CreationDoor"
        initCreationDoor(self);
    elseif type == GRID_TYPE_CALL_SUMMON then
        -- 召唤兽召唤建筑
        require "game/ui/form/dungeon/grid/CallSummon"
        initCallSummon(self);
    elseif type == GRID_TYPE_NOT_FOUND_BUILDING then
        -- 不存在指定建筑时可点击的建筑
        require "game/ui/form/dungeon/grid/NotFoundBuilding"
        initNotFoundBuilding(self);
    elseif type == GRID_TYPE_CONFUSED_SLIME then
        -- 混乱的冈布奥
        require "game/ui/form/dungeon/grid/ConfusedSlime"
        initConfusedSlime(self);
    elseif type == GRID_TYPE_ART_STATUE then
        -- 艺术雕像
        require "game/ui/form/dungeon/grid/artStatue"
        initArtStatue(self);
    elseif type == GRID_TYPE_LIGHTHOUSE then
        -- 灯塔
        require "game/ui/form/dungeon/grid/lighthouse"
        initLighthouse(self);
    else
        assert(false, "unknown grid type:"..type);
    end

    -- 如果格子已经被污染，显示污染状态
    if not self.gridData:isOpened() and self.gridData.polluted then
        local round = CombatM.getRound();
        local leftRound = self.gridData.polluted[4] - round;
        local pollutedType = self.gridData.polluted[5];

        self:gotoPolluted(pollutedType);
        self:updatePollutedLeftRound(leftRound, pollutedType);
    end

    -- 如果是boss层，直接加上
    local dungeonId = DungeonM.getDungeonId();
    local layer = DungeonM.currentLayer();
    local class = self.gridData.class;
    local dbase = DungeonM.query(class, "dbase") or {};
    if dbase["top_appear"] == 1 and DungeonBossM.isBossLayer(dungeonId, layer)
        and class ~= GRID_TYPE_ACTIVITY_BOX then
        local oldImg  = self.item:getChildByName("prison_icon");
        -- 当前未绘制监狱图标才进行绘制
        if not oldImg then
            local prisonIcon = getLevelItemIconPath("prison");
            -- 神龙夹层的学徒，另用一个监狱图标
            if UnrealDungeonM.isInUnreal() then
                prisonIcon = getLevelItemIconPath("prison2");
            end
            local prisonImg  = ccui.ImageView:create(prisonIcon);
            prisonImg:setName("prison_icon");

            self.item:addChild(prisonImg);
        end
    end
end

-- 获取道具图标节点
function UIGrid:getItemNode()
    return self.item;
end

-- 获取怪物节点
function UIGrid:getMonsterNode()
    return self.monsterNode;
end

-- 清空格子
function UIGrid:cleanItem()
    self.item:removeAllChildren();
    self.icon = nil;
end

-- 不可见状态
function UIGrid:gotoInvisible()
    if not self.gridData then
        return;
    end

    -- 门例外
    if self.gridData.type == GRID_TYPE_DOOR
        or self.gridData.state ~= GRID_STATE_COVER then
        return;
    end

    local blendColor = TextStyleM.TEXT_COLOR_BLEND;
    self.lock:setVisible(false);
    self.cover:setColor(blendColor);
    self.decorate:setColor(blendColor);
    self.shadow:setColor(blendColor);
    self.cover:addTouchEventListener(function (sender, eventType)
        if eventType == ccui.TouchEventType.began then
            -- 啥也干不了
        end
    end);
end

-- 可见状态
function UIGrid:gotoVisible()
    Profiler.funcBegin("gotoVisible");

    -- 门例外
    if self.gridData.type == GRID_TYPE_DOOR
        or self.gridData.state ~= GRID_STATE_COVER then
        return;
    end

    local blendColor = TextStyleM.TEXT_COLOR_NORMAL;
    self.lock:setVisible(false);
    self.cover:setColor(blendColor);
    self.decorate:setColor(blendColor);
    self.shadow:setColor(blendColor);
    self.openGridClick = function (sender, eventType)
        if eventType == ccui.TouchEventType.began then
            -- 打开格子
            if DungeonM.canOpenGrid(self.index) == GRID_OPEN_OK then
                DungeonActionM.go("open_grid", self.index);
                EventMgr.fire(event.PLAYER_MOVE, self.index);
            end
        end
    end

    AddTouchEventListener(self.cover, self.openGridClick, true);
    Profiler.funcEnd("gotoVisible");
end

-- 模型呼吸
function UIGrid:doModelBreath(iconImg)
    if not iconImg then
        return;
    end

    -- 将锚点设置在底部居中，方便播放动画
    if not self.hasSetAnimationPos then
        iconImg:setAnchorPoint(cc.p(0.5, 0));
        iconImg:setPosition(iconImg:getPositionX(),
                            iconImg:getPositionY() - iconImg:getContentSize().height / 2);
        self.hasSetAnimationPos = true;
    end

    local scale = iconImg:getScaleX();

    local function doChange()
        local scaleUp = cc.ScaleTo:create(1, 1*scale, 1*scale);
        local scaleDown = cc.ScaleTo:create(1, 1*scale, 0.95*scale);
        local function changeScalDone()
            if iconImg then
                performWithDelay(iconImg, doChange, 0.2);
            end
        end

        local callFunc = cc.CallFunc:create(changeScalDone);
        iconImg:runAction(cc.Sequence:create(scaleUp, scaleDown, callFunc));
    end
    performWithDelay(iconImg, doChange, 0.1);
end

-- 打开状态
function UIGrid:gotoOpen(isQuite)
    Profiler.funcBegin("gotoOpen");

    if  self.gridData.type ~= GRID_TYPE_DOOR
        and self.gridData.state == GRID_STATE_COVER then
        return;
    end

    self.lock:setVisible(false);

    self:showMonsterMark(false);

    -- 隐藏覆盖物
    local function hideCover()
        playFadeOutEffect(self.cover);
        playFadeOutEffect(self.shadow);
        playFadeOutEffect(self.decorate);
        playFadeOutEffect(self.poison);
    end

    hideCover();

    local function appear()
        -- 怪物出现
        if self.hasMonster then
            -- 静默开启的非图腾类
            if isQuite and not SummonM.isTotem(self.gridData) then
                if not (self.gridData.monster and self.gridData.monster:isDead()) then
                    self:switchBGNode(true);
                end
            else
                if SummonM.isTotem(self.gridData) then
                    -- 图腾不显示血条
                    AudioM.playFx("totem_app");
                    playMonsterAppearEffect(self, self.monsterNode);
                    return;
                end

                -- 怪物出现特效
                playMonsterAppearEffect(self, self.monsterNode, self.monsterBgNode, self.barNode);

                if self.gridData.monster then
                    -- 怪物出现音效
                    local classId = self.gridData.monster.classId;
                    local audioId = MonsterM.query(classId, "app_audio");
                    if audioId and audioId ~= "" then
                        AudioM.playFx(audioId);
                    end
                end

                -- 需要暂停
                self:pauseMonsterAction();
            end

            -- 执行出场发言
            self:doMonsterAppearTalk();

            return;
        end

        -- 看有没有道具
        if not self.hasItem then
            self.item:setVisible(true);
        else
            -- 有道具，播放道具出现特效
            self:itemAppear();
            self:playEffect(self, "effects/effect_31.csb", 1, nil, 0.7);
        end

        if self.needAnimation then
            -- 图标待机动作
            self:doModelBreath(self.icon)
        end
    end

    -- 播放覆盖物爆炸特效（仅非静默的情况下才播放）
    if self.gridData.type ~= GRID_TYPE_DOOR then
        if isQuite ~= true then
            if not self.hasMonster then
                self:playEffect(self, "animation/open_block.csb", 0.5, nil, 1);
            end

            -- 根据地板材质不同播放不同音效
            local fx = "grid2";
            fx = AudioM.queryCoverFx(self.coverPath);
            AudioM.playFx(fx);
        end

        performWithDelay(self, appear, 0.01);
    end

    -- 如果是电视机，只有当广告准备就绪时才显示
    if self.gridData.type == GRID_TYPE_OLD_TV then
        if not AdM.isAdReady() then
            cclog("广告尚未装备就绪，隐藏电视机格子。");
            self.item:removeAllChildren();
        end
    end

    Profiler.funcEnd("gotoOpen");
end

-- 道具出现
function UIGrid:itemAppear()
    self.item:setVisible(true);
    self.item:setOpacity(255);

    if self.icon then
        -- 大小伸缩动画
        local scaleUp = cc.ScaleTo:create(0.25, 1.2);
        local scaleDown = cc.ScaleTo:create(0.15, 1);
        self.icon:runAction(cc.Sequence:create(scaleUp, scaleDown));
    end

    if self.hasShadow then
        -- 道具上下浮动
        local function moveItem()

            local function doItemMove()
                local offsetY = self.iconOffsetY or 35;
                local moveUp = cc.MoveTo:create(1, cc.p(0, offsetY));
                local moveDown = cc.MoveTo:create(1, cc.p(0, offsetY - 10));
                local function moveDone()
                    if self.icon then
                        performWithDelay(self.icon, doItemMove, 0.2);
                    end
                end

                local callFunc = cc.CallFunc:create(moveDone);
                self.icon:runAction(cc.Sequence:create(moveUp, moveDown, callFunc));
            end

            if self.icon and not self.icon.isMoving then
                self.icon.isMoving = true;
                performWithDelay(self.icon, doItemMove, 0.1);
            end
        end

        if self.icon then
            local delay = math.random(1, 50) / 100;
            performWithDelay(self.icon, moveItem, delay);
        end

        -- 检查时间停止效果
        checkTimeCeaseEffectForNode(self:getItemNode());
    end
end

-- 锁住状态
function UIGrid:gotoLocked()
    Profiler.funcBegin("gotoLocked");

    if self.gridData.type == GRID_TYPE_DOOR
        or self.gridData.state ~= GRID_STATE_COVER then
        return;
    end

    --self.lock:setVisible(true);
    playFadeInEffect(self.lock)
    self.lock:addTouchEventListener(function (sender, eventType)
        if eventType == ccui.TouchEventType.began then
            -- 啥也干不了
        end
    end);

    Profiler.funcEnd("gotoLocked");
end

-- 解锁状态
function UIGrid:gotoUnlocked()
    playFadeOutEffect(self.lock);
end

-- 设置污染格剩余回合数
function UIGrid:updatePollutedLeftRound(leftRound, pollutedType)
    local leftRoundLabel = self.poison:getChildByName("left_round");

    if pollutedType == "burned" then
        TextStyleM.setTextStyle(leftRoundLabel, TextStyleM.TEXT_SIZE_LARGE2, TextStyleM.TEXT_COLOR_WHITE, true, nil, nil, true);
    else
        TextStyleM.setTextStyle(leftRoundLabel, TextStyleM.TEXT_SIZE_LARGE2, TextStyleM.TEXT_COLOR_GRAY, true, nil, nil, true);
    end
    leftRoundLabel:setString(tostring(leftRound));
    commonTextEffect3(leftRoundLabel, 1, 1, 1.0, 0.8);
    TextStyleM.setOutlineStyle(leftRoundLabel, 3, TextStyleM.TEXT_OUTLINE_COLOR_DARK);
end

-- 更新污染状态
function UIGrid:gotoPolluted(pollutedType)
    local effectNode = findChildByName(self.poison, "poison_effect");
    local function delayPlay()
        local effectId;
        if pollutedType == "burn" then
            effectId = 1346;
        else
            effectId = 1347;
        end
        self.poisonEffect = playEffect(effectNode, effectId);

        performWithDelay(effectNode, delayPlay, math.random(60, 110) / 10);
    end
    performWithDelay(effectNode, delayPlay, math.random(20, 80) / 10);

    local polluteNode = findChildByName(self.poison, "poison_img");
    if pollutedType == "burn" then
        polluteNode:loadTexture("images/ui/level/burn.png", 1);
    else
        polluteNode:loadTexture("images/ui/level/poison.png", 1);
    end
    self.poison:setVisible(true);
end

-- 格子被净化
function UIGrid:gotoPurge()
    pauseNode(self.poison);
    self.poison:setVisible(false);

    local effectNode = findChildByName(self.poison, "poison_effect");
    effectNode:stopAllActions();
    effectNode:removeAllChildren();
end

-- 技能目标选择状态
function UIGrid:gotoSelectState(isTarget, desc)
    if self.monsterNode.monsterDisplay ~= nil then
        -- 暂时隐藏格子上的怪物
        self.monsterNode.monsterDisplay:setVisible(false);
        self.monsterNode.monsterDisplay.hideDesc = desc;
        if self.csEffectNode then
            self.csEffectNode:setVisible(false);
        end

        if self.monsterNode.accompanyDisplay then
            self.monsterNode.accompanyDisplay:setVisible(false);
        end
    end
end

-- 清除技能目标选择状态
function UIGrid:clearSelectState(desc)
    if self.monsterNode.monsterDisplay ~= nil then
        if self.monsterNode.monsterDisplay.hideDesc then
            if self.monsterNode.monsterDisplay.hideDesc == desc then
                self.monsterNode.monsterDisplay.hideDesc = nil;
                self.monsterNode.monsterDisplay:setVisible(true);

                if self.csEffectNode then
                    self.csEffectNode:setVisible(true);
                end

                if self.monsterNode.accompanyDisplay then
                    self.monsterNode.accompanyDisplay:setVisible(true);
                end
            end
        else
            if not desc then
                self.monsterNode.monsterDisplay:setVisible(true);

                if self.csEffectNode then
                    self.csEffectNode:setVisible(true);
                end

                if self.monsterNode.accompanyDisplay then
                    self.monsterNode.accompanyDisplay:setVisible(true);
                end
            end
        end
    end
end

-- 打开下一层入口
function UIGrid:openEntry(isQuite)
    -- 是否静默打开
    local isQuite = isQuite or false;

    local dungeonId = DungeonM.getDungeonId();
    local layer     = DungeonM.currentLayer();

    -- 变成打开的样子
    local iconPath = getLevelItemIconPath("door");

    if not isQuite then
        if DungeonM.isMaxLayer() or DungeonInstanceM.isInInstance() or UnrealDungeonM.isInUnreal() then
            iconPath = getLevelItemIconPath("portal");
            portalEffect(self.icon, 60, 66);
        elseif SkyInstanceM.isInSkyInstance() and FormulaM.invoke("IS_SHOW_PORTAL_LOCK", dungeonId, layer) then
            iconPath = getLevelItemIconPath("portal");
            portalEffect(self.icon, 60, 66);
        else
            openDoorEffect(self.icon, 0, 5);
        end
    else
        if DungeonM.isMaxLayer() or DungeonInstanceM.isInInstance() or UnrealDungeonM.isInUnreal() then
            iconPath = getLevelItemIconPath("portal");
            portalEffect(self.icon, 60, 66);
        end
    end

    self.icon:loadTexture(iconPath);
    self.canEnterNextLevel = true;

    -- 检查时间停止效果
    checkTimeCeaseEffectForNode(self:getItemNode());
end

-- 初始化副本入口
function UIGrid:initDoor()
    self.item:removeAllChildren();

    local function onClicked(sender, eventType)
        if eventType == ccui.TouchEventType.ended then
            -- 网络状态检查
            -- 如果网络断开，提示玩家进行重新连接，连接后可再次点击进入下一层继续进行游戏
            if not Socket.isConnected() then
                LoginM.tryReconnect(getLocStr("warning_title"), getLocStr("disconnect_tip"));
                return;
            end

            -- 检测英雄是否还活着
            local hp = ME.user:getHp();
            if hp <= 0 then
                return;
            end

            -- 如果在副本中可随时退出
            if DungeonInstanceM.isInInstance() then
                local leaveInstance = function()
                    DungeonActionM.go("leave_instance");
                end

                AudioM.playFx("button_click");
                if DungeonInstanceM.isConfirmToLeave() then
                    -- 获取目标格子
                    local grid = DungeonInstanceM.getTargetGrid();
                    local auto_take = true;

                    -- 电椅不能自动领取
                    if grid and grid.type == GRID_TYPE_CHAIR then
                        auto_take = false;
                    end

                    -- 自动领取奖励
                    local function autoTakeBonus()
                        -- 获取目标格子
                        if not grid then
                            return;
                        end

                        if auto_take then
                            DungeonActionM.go("take_instance_bonus", grid:getPos());

                            -- 1 ~ 5如果有奖励
                            for i = 1, 5 do
                                grid = DungeonM.getGridByPos(i);
                                if grid.bonus then
                                    DungeonActionM.go("do_building_bonus", i);
                                end
                            end

                            -- 定时离开副本
                            performWithDelay(self, leaveInstance, 3);
                        else
                            leaveInstance();
                        end
                    end

                    AudioM.playFx("market_open_child");

                    if not auto_take then
                        autoTakeBonus();
                    else
                        confirm2(CONFIRM_TYPE_OK_CANCEL, getLocStr("slime_note"),
                            getLocStr("bonus_not_taken_tip"), autoTakeBonus);
                    end
                else
                    -- 直接离开副本
                    leaveInstance();
                end

                return;
            end

            -- 神龙夹层迷宫中
            if UnrealDungeonM.isInUnreal() then
                -- 判断是否有奖励未拾取
                local unrealId = UnrealDungeonM.getUnrealId();
                local dbase = UnrealDungeonM.query(unrealId, "dbase");

                if dbase["brave_vs_dragon"] == 1 then
                    local canLeave = true;
                    -- 勇者斗恶龙夹层内
                    if DungeonM.getDungeonMixedField("cave_light_up") == 1 and
                        BraveVSDragonM.isInSwampCave() and
                        not BraveVSDragonM.hasRescuedPrincess() then
                        -- 沼泽洞窟中，需要解救公主后才能出去
                        canLeave = false;
                    elseif DungeonBossM.isBossLayer(dungeonId, layer, ME.user) and
                        not DungeonM.isAllMonsterDead()then
                        -- 如果在勇者斗恶龙的BOSS战中，需要击杀所有怪物后才能出去
                        canLeave = false;
                    end

                    if not canLeave then
                        -- 播放门晃动的效果
                        AudioM.playFx("push_door");
                        playDoorShakeEffect(self.icon, 0.04, 3, 2);
                        return;
                    end
                end

                if UnrealDungeonM.hasBonusNotPickedUp() then
                    local function callback()
                        local function leaveUnreal()
                            DungeonActionM.go("leave_unreal");
                        end

                        if ActivityDungeonM.isInActivityDungeon()
                            and dbase["auto_pick_bonus"] == 1 then
                            wait(3);

                            -- 活动副本需要帮玩家自动拾取
                            ActivityDungeonM.autoPickItem();

                            -- 定时离开副本
                            performWithDelay(self, leaveUnreal, 3);
                        else
                            leaveUnreal();
                        end
                    end

                    AudioM.playFx("market_open_child");

                    -- 弹出确认提示框
                    local msg = getLocStr("bonus_not_taken_tip");
                    if ArenaM.isInArena() then
                        msg = getLocStr("arena_bonus_not_taken_tip");
                    end
                    confirm2(CONFIRM_TYPE_OK_CANCEL, getLocStr("slime_note"), msg, callback);
                else
                    DungeonActionM.go("leave_unreal");
                end

                return;
            end

            -- 不能进入下一层，不处理
            if not self.canEnterNextLevel then
                -- 晃动音效
                AudioM.playFx("push_door");

                -- 晃动效果
                playDoorShakeEffect(self.icon, 0.04, 3, 2);
                return;
            end

            -- 用于大地之门之类回退的判断
            -- 神龙夹层上面已经直接离开，此处不再判断
            local needCost = ME.user.dbase:queryTemp("bossLayerCostLife") or false;
            -- SLIMED-823,关闭BOSS层扣除体力的功能
            needCost = false;

            -- 引用SLIMED-823需求, 关闭BOSS层扣除体力的功能
            needCost = false;

            if DungeonBossM.isInBossLayer() then
                -- BOSS层需要扣除体力,活动副本不扣除
                if DungeonBossM.haveBonus() then
                    -- 有boss战奖励未领取
                    local function callback()
                        if not ActivityDungeonM.isInActivityDungeon() and needCost then
                            -- 体力扣除提示
                            showCostLifeInDungeon();
                        else
                            local function enterNext()
                                -- 进入下一关
                                EventMgr.fire(event.TRY_ENTER_NEXT_FLOOR);
                            end

                            if ActivityDungeonM.isInActivityBossLayer() then
                                wait(3);

                                -- 活动副本需要帮玩家自动拾取
                                ActivityDungeonM.autoPickItem();

                                -- 定时离开副本
                                performWithDelay(self, enterNext, 3);
                            else
                                enterNext();
                            end
                        end
                    end

                    AudioM.playFx("market_open_child");

                    -- 弹出确认提示框
                    local msg = getLocStr("bonus_not_taken_tip");
                    confirm2(CONFIRM_TYPE_OK_CANCEL, getLocStr("slime_note"), msg, callback);

                    return;
                elseif not ActivityDungeonM.isInActivityDungeon() and needCost then
                    -- 体力扣除提示
                    showCostLifeInDungeon();
                    return;
                end
            end

            -- 有日常任务未完成
            local taskInfo = DailyTaskM.getUnfinishDailyTask();
            -- 有日常任务未完成
            if type(taskInfo) == "table" and type(taskInfo.id) == "number" then
                local taskId = taskInfo.id;
                local taskType = DailyTaskM.query(taskId, "type");
                -- 如果本层有日常任务未完成，则要弹出提示
                if taskInfo.is_finish ~= 1 then
                    local function callback()
                        -- 进入下一关
                        EventMgr.fire(event.TRY_ENTER_NEXT_FLOOR);
                    end

                    local title = getLocStr("circus_title");
                    if taskType == "find_item" then
                        local msg = string.format(getLocStr("daily_task_notice6"), ItemM.query(DailyTaskM.query(taskId, "task_item"), "name"));
                        confirm2(CONFIRM_TYPE_OK_CANCEL, title, msg, callback);
                    elseif taskType == "snares_animal" then
                        local msg = string.format(getLocStr("daily_task_notice7"), MonsterM.query(DailyTaskM.query(taskId, "task_monster"), "name"));
                        confirm2(CONFIRM_TYPE_OK_CANCEL, title, msg, callback);
                    else
                        callback();
                    end
                end
            -- 有遗产尚未领取
            elseif DungeonLegacyM.getLegacyInfo() then
                local function callback()
                    -- 清除遗产
                    DungeonActionM.go("clear_legacy");

                    -- 进入下一关
                    EventMgr.fire(event.TRY_ENTER_NEXT_FLOOR);
                end

                -- 弹出确认提示框
                local msg = getLocStr("legacy_not_take_tip");
                confirm2(CONFIRM_TYPE_OK_CANCEL, getLocStr("slime_legacy_title"), msg, callback);

            -- 活动副本判断一下召唤兽建筑
            elseif ActivityDungeonM.isInActivityDungeon() and SummonM.hasCallSummonBuilding() then
                local function callback()
                    -- 进入下一关
                    EventMgr.fire(event.TRY_ENTER_NEXT_FLOOR);
                end
                confirm2(CONFIRM_TYPE_OK_CANCEL, getLocStr("slime_note"),
                    getLocStr("call_summon_building_tip"), callback);
            else
                -- 进入下一关
                EventMgr.fire(event.TRY_ENTER_NEXT_FLOOR);
            end
        end
    end

    local iconPath;
    local dungeonId = DungeonM.getDungeonId();
    local layer     = DungeonM.currentLayer();
    local maxFloor  = DungeonAreaM.query(dungeonId, "max_floor");

    if ArenaM.isInArena() then
        -- 角斗台，不需要锁
        iconPath = getLevelItemIconPath("portal");
    elseif ActivityDungeonM.isInActivityBossLayer() then
        -- 活动副本BOSS中，不需要锁
        iconPath = getLevelItemIconPath("portal_lock");
    elseif DungeonInstanceM.isInInstance() or
        UnrealDungeonM.isInUnreal() or
        maxFloor > 0 and layer >= maxFloor then
        -- 副本中显示锁住的传送门
        -- 关卡最后一层也显示所锁住的传送门
        iconPath = getLevelItemIconPath("portal_lock");

    elseif SkyInstanceM.isInSkyInstance() and FormulaM.invoke("IS_SHOW_PORTAL_LOCK", dungeonId, layer) then
        -- 天空副本有的需要特殊显示
        iconPath = getLevelItemIconPath("portal_lock");
    else
        -- 默认初始化的时候门是锁住的
        iconPath = getLevelItemIconPath("lock");
    end

    -- 设置一层透明的图来缩小门的响应
    local smallIcon = ccui.ImageView:create(iconPath);
    smallIcon:setScaleX(0.6);
    smallIcon:setScaleY(0.4);
    smallIcon:setOpacity(0);
    smallIcon:setTouchEnabled(true);
    smallIcon:addTouchEventListener(onClicked);

    self.icon = ccui.ImageView:create(iconPath);
    self.icon:setTouchEnabled(true);
    self.onDoorClicked = onClicked;
    self.item:addChild(self.icon);
    self.item:addChild(smallIcon)

    if UnrealDungeonM.isInUnreal() then
        -- 是在夹层
        local unrealId = UnrealDungeonM.getUnrealId();
        local dbase = UnrealDungeonM.query(unrealId, "dbase");
        if type(dbase) == 'table' and
           (dbase["door_appear_by_clear"] == 1 or dbase["door_appear_by_compound"]) == 1 then
            -- 配置了通关或者完成合成时才出现传送门
            self.item:setVisible(false);
        elseif BraveVSDragonM.isInSwampCave() then
            -- 如果在沼泽洞窟中，只有救出公主后，门才打开
            if BraveVSDragonM.hasRescuedPrincess() then
                self.item:setVisible(true);
                self:openEntry(true);
            else
                self.item:setVisible(true);
            end
        elseif dbase["brave_vs_dragon"] and
            DungeonBossM.isBossLayer(dungeonId, layer, ME.user) and
            not DungeonM.isAllMonsterDead() then
            -- 如果在勇者斗恶龙的BOSS战中，需要击杀所有怪物后门才打开
            self.item:setVisible(true)
        else
            self.item:setVisible(true);
            self:openEntry(true);
        end
    elseif DungeonInstanceM.isInInstance() then
        -- 副本中，门总是开启的
        self.item:setVisible(true);
        self:openEntry(true);
    elseif ActivityDungeonM.isInActivityBossLayer() then
        -- 特殊的活动副本层
        self.item:setVisible(true);
        if DungeonM.isDoorOpened() == true then
            -- 变成打开的样子
            self:openEntry(true);
        end
    elseif BraveVSDragonM.isInBraveVSDragon() then
        -- 勇者斗恶龙的副本中，如果符合门开启的条件，则门需要开启
        self.item:setVisible(true);
        if BraveVSDragonM.canOpenDoor() then
            self:openEntry(true);
        end
    else
        self.item:setVisible(true);
        -- 如果门已经开过了
        if DungeonM.isDoorOpened() == true then
            -- 变成打开的样子
            self:openEntry(true);
        end
    end
end

-- 初始化障碍
function UIGrid:initBlock()
    self.item:removeAllChildren();

    local function onClicked(sender, eventType)
        if eventType == ccui.TouchEventType.began then
            -- 啥也干不了
        end
    end

    -- 如果没有element，还是显示成空格的样子
    if self.gridData.element == 0 then
        return;
    end

    local class = self.gridData.class;
    local dbase = DungeonM.query(class, "dbase");
    if type(dbase) == "table" and dbase["invisible"] == 1 then
        -- 配置的是无形的障碍，无需显示图标
        return;
    end

    -- 障碍图标
    local style = DungeonAreaM.query(DungeonM.getDungeonId(), "style");
    local pos   = self.gridData:getPos();
    local elementDbase = DungeonM.queryElement(self.gridData.element, "dbase");
    if type(elementDbase) == 'table' and type(elementDbase["icon"]) == 'string' then
        -- 配置了自定义图标
        self.icon = ccui.ImageView:create(getLevelItemIconPath(elementDbase["icon"]));
    elseif style == "lava" or style == "fortress" then
        if pos >= 1 and pos <= 5 then
            self.icon = ccui.ImageView:create(getLevelItemIconPath("block2_2"));
        else
            self.icon = ccui.ImageView:create(getLevelItemIconPath("block2"));
        end
    else
        if pos >= 1 and pos <= 5 then
            self.icon = ccui.ImageView:create(getLevelItemIconPath("block_2"));
        else
            self.icon = ccui.ImageView:create(getLevelItemIconPath("block"));
        end
    end
    self.icon:setTouchEnabled(true);
    self.icon:addTouchEventListener(onClicked);
    self.icon:setPositionY(-5);
    self.icon:setVisible(true);
    self.item:addChild(self.icon);

    -- 障碍上的特效
    local dbase = DungeonAreaM.query(DungeonM.getDungeonId(), "dbase");
    local blockEffectConfig = dbase["block_effect"];
    if blockEffectConfig ~= nil then
        local blockEffectId = blockEffectConfig["id"] or 0;    -- 特效ID
        local rangeX = blockEffectConfig["range_x"] or {0, 0}; -- X轴的范围
        local rangeY = blockEffectConfig["range_y"] or {0, 0}; -- Y轴的范围
        local num = blockEffectConfig["num"] or 0;             -- 特效个数

        for i = 1, num do
            local function playBlockEffect()
                playEffect(self.item, blockEffectId, math.random(rangeX[1], rangeX[2]), math.random(rangeY[1], rangeY[2]));
                performWithDelay(self.item, playBlockEffect, math.random(2, 4));
            end
            performWithDelay(self.item, playBlockEffect, math.random(2, 4));
        end
    end

    -- 如果是岩浆障碍，并且已被消除，则隐藏
    if elementDbase["magma_obstacle"] == 1 and BraveVSDragonM.hasRemovedMagaMaObstacle() then
        self.icon:setVisible(false);
    end
end

-- 执行祭坛操作的回调
function UIGrid:onDoAltarEvent()
    playBuildingDisappear(self);
end

-- 领取遗产的回调
function UIGrid:onTakeLegacy(legacy)
    onTakeLegacy(self, legacy);
end

-- 占领矿脉的回调
function UIGrid:onOccupyMine()
    playBuildingDisappear(self);
end

-- 领取幸运喷泉奖励的回调
function UIGrid:onTakeFountainBonus(bonus)
    -- 播放建筑奖励特效
    buildingBonusEffect(self);

    self:playBonusListEffect(bonus);
end

-- 仙女对话
function UIGrid:onFairyTalk(para)
    initPinocchioFairy(self);
end

-- 完成未来猫任务
function UIGrid:onFinishDoraemonTask(bonus)
    -- 播放建筑奖励特效
    buildingBonusEffect(self);

    self:playBonusListEffect(bonus);
end

-- 播放多个奖励拾取特效
function UIGrid:playBonusListEffect(bonusList, isDisappear, isFirstDelay, noAlert, curNum, borrowGrid)
    local bonusTime = 0;
    if isFirstDelay == nil then
        -- 第一次奖励特效是否延时播放，默认为是
        isFirstDelay = true
    end
    if #bonusList > 0 then
        -- 数组形式的奖励

        if type(bonusList[1]) == 'number' then
            -- 如果是钻石，并且数量是5的整数倍，则分成多次飞入
            local gemPerTimes = 5;
            if bonusList[2] == "gem" and bonusList[3] > gemPerTimes and bonusList[3] % gemPerTimes == 0 then
                local bonus = { 2, "gem", gemPerTimes, };
                local times = bonusList[3] / gemPerTimes;
                bonusList = {};
                for i = 1, times do
                    table.insert(bonusList, bonus);
                end
            else
                -- 只有一种奖励时，需要转换一下格式
                bonusList = { bonusList };
            end
        end

        for i, bonus in pairs(bonusList) do
            local function callback2()
                self:onPickUp(bonus, nil, true, noAlert, curNum, borrowGrid);
            end
            if isFirstDelay then
                performWithDelay(self, callback2, PICK_INTERVALE*(i));
            else
                performWithDelay(self, callback2, PICK_INTERVALE*(i - 1));
            end
        end
        bonusTime = PICK_INTERVALE * #bonusList;
    elseif type(bonusList["bonus"]) == 'table' and #bonusList["bonus"] > 0 then
        if type(bonusList["bonus"][1]) == 'number' then
            -- 只有一种奖励
            local function callback2()
                self:onPickUp(bonusList["bonus"], nil, true, noAlert, nil, borrowGrid);
            end
            performWithDelay(self, callback2, PICK_INTERVALE);
            bonusTime = PICK_INTERVALE;
        else
            -- 有多种奖励
            for i, bonus in pairs(bonusList["bonus"]) do
                local function callback2()
                    self:onPickUp(bonus, nil, true, noAlert, nil, borrowGrid);
                end
                if isFirstDelay then
                    performWithDelay(self, callback2, PICK_INTERVALE*(i));
                else
                    performWithDelay(self, callback2, PICK_INTERVALE*(i - 1));
                end
            end
            bonusTime = PICK_INTERVALE * #bonusList["bonus"];
        end
    end

    if isDisappear == nil then
        -- 默认需要播放建筑消失
        isDisappear = true;
    end

    if isDisappear then
        local function disappear()
            -- 播放建筑消失特效
            playBuildingDisappear(self);
        end
        performWithDelay(self, disappear, bonusTime);
    end

    return bonusTime;
end

-- 领取地牢副本奖励的回调
function UIGrid:onTakeInstanceBonus(bonusList)
    local function showBonusHint(gridType)
        local class = self.gridData.class;
        local buildingInfo = BuildingBonusM.queryBuildingView(class);

        if not buildingInfo then
            return;
        end

        -- 如果配置了奖励提示
        if buildingInfo["bonus_tip"] then
            local list = splitString(buildingInfo["bonus_tip"], "\n");
            splitAlert(list);
        end

        -- TODO: 点门离开时走的take_instance_bonus流程，导致没有提示
        if bonusList.tip then
            local list = splitString(bonusList.tip, "\n");
            splitAlert(list);
        end
    end

    local function callback()
        -- 如果没有获得奖励，直接移除格子
        if not bonusList then
            self.item:removeAllChildren();
            return;
        end

        local gridType = self.gridData.type;
        showBonusHint(gridType);

        -- 如果配置了特效
        local effectId = self.gridData.effect_id;
        if type(effectId) == "number" then
            local offset = self.gridData.effect_offset or {0, 0};
            playEffect(self, effectId, offset[1], offset[2]);
        elseif type(effectId) == "string" then
            playBuildingDisappearEffect(self, effectId);
        else
            -- 播放建筑奖励特效
            buildingBonusEffect(self);
        end

        -- 提示获得了状态
        if bonusList.status then
            self:hintGotDebuff(bonusList.status);
        end

        self:playBonusListEffect(bonusList);
    end

    -- 短暂停顿，等界面消失后开始播放
    performWithDelay(self, callback, 0.3);
end

-- 装备升级的回调
function UIGrid:onUpgradeEquip()
    playBuildingDisappear(self);
end

-- 怪物全清的回调
function UIGrid:onAllMonsterKilled()
    -- 将格子上的监狱图标移除
    local prisonImg  = self.item:getChildByName("prison_icon");
    self.item:removeChild(prisonImg);

    -- 如果是冈布奥学徒，需要替换成笑脸
    if self.gridData.type == GRID_TYPE_APPRENTICE then
        -- 换成新的图标
        local apprenticeIcon = getLevelItemIconPath(self.gridData.icon2);
        self.icon:loadTexture(apprenticeIcon);
        self.icon:setTouchEnabled(true);
        self.icon:addTouchEventListener(self.pickApprentice);
    end
    if self.gridData.is_apprentice then
        -- 换成新的图标
        local apprenticeIcon = getLevelItemIconPath(self.gridData.icon2);
        self.icon:loadTexture(apprenticeIcon);
    end

    -- 如果是boss掉落的
    if self.appearWhenBossDie then
        self:initItem(self.gridData);
        self:itemAppear();
    end
end

-- 世界树之枝拾取后处理
function UIGrid:onPickupFeatureItem()
    local bonusList = DungeonActionM.go("pick_feature_item", self.gridData:getPos());

    -- 将格子设置为无效
    playBuildingDisappear(self);

    self:playBonusListEffect(bonusList);
    self:showBuildingBonusTip();
end

-- 蜂巢消失的事件回调
function UIGrid:onBeehiveDisappear()
    -- 播放建筑消失特效
    playBuildingDisappear(self);
end

-- 格子渐隐消失
function UIGrid:gridFadeOut()
    if self.iconPath then
        local image = ccui.ImageView:create();
        image:loadTexture(self.iconPath);
        self:addChild(image);

        local fadeOut = cc.FadeOut:create(1);
        image:runAction(fadeOut);
    end
end

-- 发动陷阱的事件回调
function UIGrid:onLanchTrap()
end

-- 陷阱消失
function UIGrid:onTrapDisappear(delay)
    local trapId = self.gridData.id;
    local trapAlias = CommonTrapM.query(trapId, "alias");

    -- 如果是元素漩涡，这里不做处理
    if trapAlias == "element_swirl" or trapAlias == "blood_trap" or trapAlias == "time_bomb" then
        return;
    end

    -- 延迟时间
    local delayTime = delay or 1;

    -- 如果是炎龙刃
    if trapAlias == "fire_blade" then
        -- 播放炎龙刃消失特效
        local animation = self.animation;
        playAnimEffectDisappear(self, animation, 15, 38);
        return;
    end

    -- 播放建筑消失特效
    local function callback()
        -- 可能已经转为其他类型的格子了，这里需要判断下状态
        if self.gridData.type ~= GRID_TYPE_TRAP or
           self.gridData.state ~= GRID_STATE_DISABLE then
            return;
        end

        playBuildingDisappear2(self);
        AudioM.playFx("trap_disapp");
    end
    performWithDelay(self, callback, delayTime);
end

-- 格子消失
function UIGrid:onGridDisappear()
    local grid = self.gridData;

    if grid.state ~= GRID_STATE_DISABLE then
        return;
    end
end

-- 矿山消失
function UIGrid:onMineHillDisappear()
    local grid = self.gridData;

    -- TODO: 已经变为其他格子的暂时无解
    self.barNode:setVisible(false);

    -- 隐藏hp
    --self.barNode:setVisible(false);
    -- local hpNode = findChildByName(self.barNode, "hp_node");
    -- hpNode:setVisible(false);

    -- 由于逻辑层和UI层的格子类型可能不一致，以逻辑层为准
    grid = DungeonM.getGridByPos(grid:getPos());

    -- 可能已经转为其他类型的格子了，这里需要判断下状态
    if grid.type ~= GRID_TYPE_MINE_HILL or grid.state ~= GRID_STATE_DISABLE then
        return;
    end

    playBuildingDisappear(self);
end

-- 图腾消失
function UIGrid:onTotemDisappear()
    if not SummonM.isTotem(self.gridData) or self.gridData.state ~= GRID_STATE_DISABLE then
        return;
    end

    -- 播放建筑消失特效
    playBuildingDisappear(self);
end

-- 采摘果树的事件回调
function UIGrid:onTakeFruit(bonusInfo)
    onTakeFruit(self, bonusInfo);
end

-- 领取冒险者残骸奖励的事件回调
function UIGrid:onTakeWreckage(bonusList)
    onTakeWreckage(self, bonusList);
end

-- 蒸汽机械
function UIGrid:redrawSteamMachine()
    initSteamMachine(self);
    self:updateCountDown();
end

-- 埋葬水手的事件回调
function UIGrid:onBurySailor(args)
    self:playBonusListEffect({});
    --self.item:removeAllChildren();
end

-- 提示获得了减益状态
function UIGrid:hintGotDebuff(status)
    local debuff = status.prop or {};
    if PropM.getPropId("poisoned") == debuff[1] then
        -- 如果真的有中毒
        local prop = PropM.combine(ME.user, debuff[1], debuff[2]);

        -- 中毒
        if prop[3] > 0 then
            alert(getLocStr("got_poisoned"));
        end
    elseif PropM.getPropId("curse") == debuff[1] then
        -- 诅咒
        --alert(getLocStr("got_curse"));
    end
end

-- 村民奖励事件
function UIGrid:onVillagerEvent(bonusList)
    if bonusList then
        -- 如果有奖励，播放奖励特效
        self:playBonusListEffect(bonusList, self.gridData.state == GRID_STATE_DISABLE);
    else
        -- 否则，直接移除格子
        self.item:removeAllChildren();
    end
end

-- 海贼宝藏奖励事件
function UIGrid:onTreasureEvent(bonusList)
    if bonusList then
        -- 如果有奖励，播放奖励特效
        self:playBonusListEffect(bonusList);
    else
        -- 否则，直接移除格子
        self.item:removeAllChildren();
    end
end

-- 翻找垃圾堆的事件回调
function UIGrid:onRubbishEvent(bonusInfo)
    local bonusList = bonusInfo.bonus;
    if bonusList then
        self:playBonusListEffect(bonusList);
    end

    if bonusInfo.status then
        -- 提示中毒
        self:hintGotDebuff(bonusInfo.status);
    elseif bonusList[1] == 1 then
        -- 如果获得一种物品，则给提示
        local classId = bonusList[2];
        local amount  = bonusList[3];
        local name = ItemM.query(classId, "name");
        local unit = ItemM.query(classId, "unit");
        local msg = string.format(getLocStr("find_item_in_rubish"), amount, unit, name);
        alert(msg);
    else
        -- 领奖提示
        self:showBuildingBonusTip();
    end
end

-- 雕像的事件回调
function UIGrid:onStatueEvent(bonusInfo)
    -- 提示收到雕像关注
    alert(getLocStr("statue_tip"));

    local function callFunc()
        local bonusList = bonusInfo.bonus;
        if bonusList then
            self:playBonusListEffect(bonusList);
        end

        if bonusInfo.status then
            self:hintGotDebuff(bonusInfo.status);
        end
    end

    performWithDelay(self, callFunc, 0.5);
end

-- 提示获得了建筑奖励
function UIGrid:showBuildingBonusTip(bonusList)
    local gridType = self.gridData.type;
    local class = self.gridData.class;
    local bonusTip;
    if gridType == GRID_TYPE_DRIFT_BOTTLE then
        -- 漂流瓶
        local id = self.gridData.id;
        if not id then
            bonusTip = BuildingBonusM.getBuildingBonusTip(class);
        else
            bonusTip = DriftBottleM.query(id, "content");
        end
        alert(bonusTip);
    elseif gridType == GRID_TYPE_EVIL_STATUE or gridType == GRID_TYPE_BROKEN_ROBOT then
        -- 邪神雕像，不需要
    elseif gridType == GRID_TYPE_MASSAGE_ROOM then
        -- 按摩房，不需要
    else
        -- 其他建筑
        bonusTip = BuildingBonusM.getBuildingBonusTip(class);
        if type(bonusTip) ~= "string" or bonusTip == "" then
            return;
        end

        -- 如果配置了显示奖励描述
        local buildingInfo = BuildingBonusM.queryBuildingView(class) or {};
        if buildingInfo["show_bonus_desc"] == 1 and bonusList ~= nil then
            -- 获取奖励描述，分多次弹出提示
            local tip = DungeonM.getBonusDesc(bonusTip, bonusList);
            if type(tip) == "string" and tip ~= "" then
                local list = splitString(tip, "\n");
                splitAlert(list);
            end
        else
            local list = splitString(bonusTip, "\n");
            splitAlert(list);
        end
    end
end

-- 建筑奖励的事件回调
function UIGrid:onBuildingBonus(bonusInfo, remainGrid, curNum, isCsdNode)
    local isDisappear = not remainGrid;
    isCsdNode = isCsdNode or false;

    local function callFunc()
        local bonusList = bonusInfo.bonus;

        -- 通用奖励提示
        self:showBuildingBonusTip(bonusList);

        -- 播放奖励特效
        if bonusList then
            self:playBonusListEffect(bonusList, isDisappear, nil, nil, curNum, true);
        elseif bonusInfo.prop then
            -- 播放建筑消失特效
            if isDisappear then
                if isCsdNode then
                    playBuildingDisappear2(self);
                else
                    playBuildingDisappear(self);
                end
            end
        elseif bonusInfo.dungeon_attrib then
            -- 播放建筑消失特效
            playBuildingDisappear(self);
        end

        -- 提示获得了状态
        if bonusInfo.status then
            self:hintGotDebuff(bonusInfo.status);
        end
    end

    performWithDelay(self, callFunc, 0.1);

    self:updateCountDown();
end

-- 被偷窃了，道具隐藏掉
function UIGrid:beStolen(keep)
    -- 如果有钥匙，就不要处理了
    if self.hasKey then
        return;
    end

    -- 如果需求保留物品
    if keep then
        return;
    end

    -- 移除道具图标
    self.item:removeAllChildren();
    self.icon = nil;
    self.itemShadow = nil;
end

-- 道具捡取回调函数
function UIGrid:onPickUp(bonus, newBonus, isItemVisible, noAlert, curNum, borrowGrid)
    Profiler.funcBegin("UIGrid:onPickUp");

    isItemVisible = isItemVisible or false;

    local uiCurLevel = UIDungeonMgr.getCurLevel();

    if not isItemVisible then
        -- 移除道具图标
        self.item:removeAllChildren();
        self.icon = nil;
        self.itemShadow = nil;
    end

    -- if self.hasKey == 1 then
    --     return;
    -- end

    -- 如果没有奖励
    if not bonus or #bonus <= 0 then
        return;
    end

    Profiler.funcBegin("UIGrid:onPickUp1");

    local str = "";
    local color = TextStyleM.TEXT_COLOR_BROWN_YELLOW;
    if bonus[1] == 1 then
        -- 物品
        local itemId = bonus[2];
        local num = bonus[3];

        local itemName = ItemM.query(itemId, "name");
        -- 奇怪的书籍名字动态获取
        if FormulaM.invoke("IS_STRANGE_BOOK", itemId) then
            itemName = CaveHideItemM.getStrangeBookName();
        end

        local itemsFlyIn;
        local delayFly = false
        if self.gridData.type == GRID_TYPE_BOX and not borrowGrid then
            -- 获取宝箱类型
            local boxType = self.gridData.boxType;
            if type(boxType) == "string" then
                openBoxEffect1(self.item, boxType, 0, 0);
                delayFly = true;
                -- 等宝箱动画播放一段时间后，再播放物品飞入效果
                performWithDelay(self, function() itemsFlyIn(); end, 0.7);

                AudioM.playFx("pick_case");
            else
                AudioM.playFx("pick_goods");
            end
        end

        itemsFlyIn = function()
            -- 飞入效果
            local fileName = ItemM.query(itemId, "icon");
            local iconPath = getItemIconPath(fileName);
            if SpellM.isSpell(itemId) then -- 卷轴
                if self.gridData.type ~= GRID_TYPE_BOX then --不是宝箱中开出的
                    AudioM.playFx("pick_spell");
                end
                pickupSpellEffect(self, UIDungeonMgr.getCurLevel():getSpellNode(), iconPath, itemId, num);
            elseif EquipM.isEquipment(itemId) then -- 宝物
                if self.gridData.type ~= GRID_TYPE_BOX then --不是宝箱中开出的
                    AudioM.playFx("equip_put");
                end
                pickupEquipEffect(self, UIDungeonMgr.getCurLevel():getEquipNode(), iconPath, itemId, num);
            elseif DragonWishM.isWishItem(itemId) then -- 龙珠
                AudioM.playFx("pick_goods");
                -- 根据玩家已经有多少个龙珠，来选择图标
                local curUINum = 0;
                local uiDungeonMain = UIMgr:getCurrentScene():getFormByName("UIDungeonMain");
                if uiDungeonMain then
                    curUINum = uiDungeonMain.dungeonTopMenu:getCurUIBallCount();
                end

                if curUINum > 7 then
                    curUINum = 7;
                end

                local wishItemIcons = { 1500, 1501, 1502, 1503, 1504, 1505, 1506, };
                local iconIndex = curUINum == 0 and 1 or curUINum;
                iconPath = getItemIconPath(wishItemIcons[iconIndex]);
                if num == 1 then
                    pickDragonBallEffect(self, UIDungeonMgr.getCurLevel().dungeonTopMenu:getDragonBallNode(), iconPath);
                else
                    local modelNode = findChildByName(UIDungeonMgr.getCurLevel().node, "CT2/model_node");
                    gainDragonBallEffect(modelNode, num);
                end
            elseif SkyResourceM.query(itemId) then -- 天空物资
                AudioM.playFx("pick_goods");
                local itemBar = uiCurLevel:getFreeItemBar();
                local iconNode = itemBar:getChildByName("icon");
                local textNode = itemBar:getChildByName("num");
                local total = (nil == curNum) and ItemM.getAmount(ME.user, itemId) or curNum;
                gainSpecialItemEffect(self, iconNode, iconPath, textNode, total, bonus[3]);
            elseif PropertyM.isProperty(itemId) then   -- 道具
                AudioM.playFx("pick_goods");
                pickupPropertyEffect(self, UIDungeonMgr.getCurLevel():getEquipNode(), iconPath, itemId, num);
            else -- 其他
                if not noAlert then
                    str = itemName;
                end
                local itemBar = uiCurLevel:getFreeItemBar();
                local iconNode = itemBar:getChildByName("icon");
                local textNode = itemBar:getChildByName("num");
                local total = (nil == curNum) and ItemM.getAmount(ME.user, itemId) or curNum;
                gainSpecialItemEffect(self, iconNode, iconPath, textNode, total, num);
                AudioM.playFx("pick_goods");
            end
        end

        -- 不需要延迟，直接播放飞的效果
        if not delayFly then
            itemsFlyIn();
        end
    elseif bonus[1] == 2 then
        -- 属性
        if bonus[2] == "hp" or bonus[2] == "max_hp" or bonus[2] == "limit_hp" then
            AudioM.playFx("pick_hp");
            gainHpEffect(self, uiCurLevel:getHpIconNode(), uiCurLevel:getHpTextNode(), bonus[3]);
        elseif bonus[2] == "mp" or bonus[2] == "max_mp" or bonus[2] == "limit_mp" then
            AudioM.playFx("pick_mp");
            gainMpEffect(self, uiCurLevel:getMpIconNode(), uiCurLevel:getMpTextNode(), bonus[3]);
        elseif bonus[2] == "attack" then
            AudioM.playFx("pick_hp");
            gainAttackEffect(self, uiCurLevel:getAttackIconNode(), uiCurLevel:getAttackTextNode(), bonus[3]);
        elseif bonus[2] == "magic" then
            AudioM.playFx("pick_mp");
            gainMagicEffect(self, uiCurLevel:getMagicIconNode(), uiCurLevel:getMagicTextNode(), bonus[3]);
        elseif bonus[2] == "money" then
            AudioM.playFx("pick_money");
            local itemBar = uiCurLevel:getFreeItemBar();
            local iconNode = itemBar:getChildByName("icon");
            local textNode = itemBar:getChildByName("num");
            local total = (nil == curNum) and ME.user.dbase:query("money", 0) or curNum;
            local iconPath = getSmallAttribIconPath("money");
            gainSpecialItemEffect(self, iconNode, iconPath, textNode, total, bonus[3]);
        elseif bonus[2] == "gem" then
            AudioM.playFx("pick_money");
            local itemBar = uiCurLevel:getFreeItemBar();
            local iconNode = itemBar:getChildByName("icon");
            local textNode = itemBar:getChildByName("num");
            local total = (nil == curNum) and ME.user.dbase:query("gem", 0) or curNum;
            local iconPath = getSmallAttribIconPath("gem");
            gainSpecialItemEffect(self, iconNode, iconPath, textNode, total, bonus[3]);
        elseif bonus[2] == "soul_collect" then
            AudioM.playFx("pick_money");
            local itemBar = uiCurLevel:getFreeItemBar();
            local iconNode = itemBar:getChildByName("icon");
            local textNode = itemBar:getChildByName("num");
            local total = (nil == curNum) and ME.user.dbase:query("soul_collect", 0) or curNum;
            local iconPath = getSmallAttribIconPath("money");
            gainSpecialItemEffect(self, iconNode, iconPath, textNode, total, bonus[3]);
        elseif bonus[2] == "gem" then
            AudioM.playFx("pick_money");
            local itemBar = uiCurLevel:getFreeItemBar();
            local iconNode = itemBar:getChildByName("icon");
            local textNode = itemBar:getChildByName("num");
            local total = (nil == curNum) and ME.user.dbase:query("gem", 0) or curNum;
            local iconPath = getSmallAttribIconPath("gem");
            gainSpecialItemEffect(self, iconNode, iconPath, textNode, total, bonus[3]);
        elseif bonus[2] == "vital_essence" then
            AudioM.playFx("pick_money");
            local itemBar = uiCurLevel:getFreeItemBar();
            local iconNode = itemBar:getChildByName("icon");
            local textNode = itemBar:getChildByName("num");
            local total = (nil == curNum) and ME.user.dbase:query("vital_essence", 0) or curNum;
            local iconPath = getSmallAttribIconPath("money");
            gainSpecialItemEffect(self, iconNode, iconPath, textNode, total, bonus[3]);
        elseif bonus[2] == "search_point" then
            AudioM.playFx("pick_search_point");
            local spIconNode = UIDungeonMgr.getCurLevel().dungeonTopMenu:getSpIconNode();
            local spTextNode = UIDungeonMgr.getCurLevel().dungeonTopMenu:getSpTextNode();
            local icon = self.gridData.icon;

            -- 如果是从怪物身上盗取的奖励，就不能用配置的icon了
            if self.gridData.type ~= GRID_TYPE_ITEM then
                icon = "search_point1";
            end
            pickupSpEffect(self, spIconNode, spTextNode, icon);

        elseif bonus[2] == "idle_worker_num" then
            AudioM.playFx("slime_swordman_app");
            str = FieldsM.getFieldName("idle_worker_num") .. " +" .. bonus[3];
        elseif bonus[2] == "bine_attrib" then
            str = getLocStr("bine_attrib_upgrade");
        else
            local name = FieldsM.getFieldName(bonus[2]) or bonus[2];
            str = string.format("%s + %d", name, bonus[3]);
        end
    elseif bonus[1] == 3 then
        -- 英雄
        if newBonus ~= nil then
            if newBonus["id"] == 3 then
                local heroName = PetM.query(newBonus["classId"], "name");
                str = string.format(getLocStr("rescue_slime_succ"), heroName);
            else
                -- 玩家已经有该英雄，改为奖励徽章碎片
                local itemName = ItemM.query(newBonus["classId"], "name");
                str = string.format(getLocStr("bonus_material_patch"), itemName, newBonus["count"]);
            end
        end
    elseif bonus[1] == 5 then
        -- 通知玩家区域解锁
        local areaId = bonus[2];
        local areaName = DungeonAreaM.query(areaId, "name");
        str = string.format(getLocStr("dungeonMapName"), areaName);
        str = str .. " +" .. bonus[3];

        --TODO 这里临时加一个比较明显的提示，等开启效果开放后，需要干掉
        --by limj SLIMED-155
        showHint(string.format(getLocStr("unlockDungeonTip"), areaName));
        --

    elseif bonus[1] == 8 then
        -- 机器人奖励
        local robotId = bonus[2];
        local itemBar = uiCurLevel:getFreeItemBar();
        local iconNode = itemBar:getChildByName("icon");
        local textNode = itemBar:getChildByName("num");
        local total = SkyRobotM.getRobotInfo(robotId)["total"];
        local fileName = SkyRobotM.query(robotId, "icon");
        local iconPath = getRobotIconPath(fileName);

        str = SkyRobotM.query(robotId, "name");

        gainSpecialItemEffect(self, iconNode, iconPath, textNode, total, bonus[3]);
        AudioM.playFx("pick_goods");
    elseif bonus[1] == 9 then
        -- 装备经验升级
        -- TODO暂不播放任何动画
    elseif type(bonus[1]) == "table" then
        AudioM.playFx("pick_goods");
        self:playBonusListEffect(bonus, false, false, noAlert, nil, borrowGrid);
    else
        assert(false, "暂不支持的奖励格式：" .. bonus[1]);
    end

    Profiler.funcEnd("UIGrid:onPickUp1");
    Profiler.funcBegin("UIGrid:onPickUp2");

    -- 飘字
    if str ~= "" then
        local text = ccui.Text:create();
        text:setFontName(getLocFontName());
        text:setFontSize(36);
        text:setString(str);
        text:setTextColor(color);
        text:setAnchorPoint(cc.p(0.5, 0.5));
        local outlineColor = TextStyleM.TEXT_COLOR_DARK_BROWN;
        text:enableOutline(cc.c4b(outlineColor[1], outlineColor[2], outlineColor[3], 255), 2);
        text:getVirtualRenderer():setAdditionalKerning(getLocKerning() -4);
        text:setScale(0.8);

        local effectParent = UIDungeonMgr:getCurLevel():getEffectParent();
        text:setPosition(cc.pAdd(cc.p(self:getPosition()), cc.p(self.item:getPosition())));

        -- 计算文本的宽度
        local charWidth = string.getWCharWidth(str, 30);

        -- 如果是第一列或者最后一列，则修正一下位置
        local gridWidth = 140;
        local offsetX = (charWidth - gridWidth)/2;
        local curColumn = self.index % GRID_COL_NUM;
        if curColumn == 1 then
            text:setPositionX(text:getPositionX() + offsetX);
        elseif curColumn == 0 then
            text:setPositionX(text:getPositionX() - offsetX);
        end

        -- 非第一排的整体往上挪一些
        if self.index > 5 then
            text:setPositionY(text:getPositionY() + 60);
        end

        local curScale = text:getScale();
        text:setScale(0.8 * curScale);

        local scaleUp = cc.ScaleTo:create(0.2, 1.2 * curScale);
        local scaleDown = cc.ScaleTo:create(0.15, 1.0 * curScale);
        local move = cc.MoveBy:create(1.0, cc.p(0, 50));
        local delay = cc.DelayTime:create(0.5);
        local fadeOut = cc.FadeOut:create(0.5);
        local callFunc2 = cc.CallFunc:create(function()
            effectParent:removeChild(text);
        end);

        local action = cc.Sequence:create(scaleUp, scaleDown, cc.Spawn:create(move, cc.Sequence:create(delay, fadeOut)), callFunc2);
        text:runAction(action);

        effectParent:addChild(text);
    end
        Profiler.funcEnd("UIGrid:onPickUp2");

    Profiler.funcEnd("UIGrid:onPickUp");
end

-- 种植魔藤的事件
function UIGrid:onPlantBine()
    onPlantBine(self);
end

-- 初始化道具
function UIGrid:initItem()
    self.item:removeAllChildren();

    local function onClicked(sender, eventType)
        if eventType == ccui.TouchEventType.began then
            -- boss层中
            local dungeonId = DungeonM.getDungeonId();
            local layer     = DungeonM.currentLayer();
            if self.gridData.need_prison == 1 and
                DungeonBossM.isBossLayer(dungeonId, layer, ME.user) and
                not DungeonM.isAllMonsterDead() then
                alert(getLocStr("pick_up_instance_target_fail"));
                return;
            end

            local result = DungeonActionM.go("pick_item", self.index);
            if true == result then
                self.pickItemClick = nil;
                EventMgr.fire(event.PLAYER_MOVE, self.index);
                return true;
            elseif type(result) == "string" then
                alert(result, false);
            end
            return false;
        end
    end

    self.pickItemClick = onClicked;

    self.hasShadow = false;

    -- 是否发光
    self.needShining = false;
    -- 发光特效偏移
    self.shiningAjustPos = cc.p(70, 100);

    local iconPath;
    local iconOffset;
    if self:isKeyGrid() then --钥匙
        iconPath = getLevelItemIconPath("key");

        -- 需要漂浮的要特殊处理
        self.hasShadow = true;

        if self.gridData.type == GRID_TYPE_MONSTER and self.gridData.is_elite == 1 then
            -- 需要显示精英怪标示
            local eliteIcon = findChildByName(self.barNode, "crown");
            eliteIcon:setVisible(true);
        end
        --self.shiningAjustPos = cc.p(0, 0);
    else
        if self.gridData.bonus == nil then
            return;
        end

        if self.gridData.type == GRID_TYPE_BOX then
            -- 需要显示宝箱怪标示
            local boxIcon = findChildByName(self.barNode, "box_icon");
            boxIcon:setVisible(true);
            iconPath = getLevelItemIconPath(self.gridData.icon);
            self.needShining = true;
            --self.shiningAjustPos = cc.p(0, 0);
        elseif self.gridData.bonus[1] == 1 then
            -- 道具奖励
            local itemId = self.gridData.bonus[2];
            local fileName = ItemM.query(itemId, "icon");
            iconPath = getItemIconPath(fileName);

            local icon = self.gridData.icon;
            if icon ~= nil then
                if type(icon) == "string" then
                    iconPath = getLevelItemIconPath(icon);
                elseif type(icon) == "number" then
                    iconPath = getItemIconPath(icon);
                end
            end

            -- 龙珠需要特殊处理
            if DragonWishM.isWishItem(itemId) then
                -- 记录下当前层刷出的龙珠数量
                if not self.hasRecord then
                    local layerCount = DragonWishM.getLayerCount() + 1;
                    DragonWishM.setLayerCount(layerCount);
                    self.hasRecord = true;
                end

                -- 根据玩家已经有多少个龙珠，来选择图标
                local wishItemCount = DragonWishM.getCurLayerWishCount();

                if wishItemCount > 6 then
                    wishItemCount = 6;
                end

                iconPath = getLevelItemIconPath(icon .. wishItemCount);

                -- 纵向偏移
                self.iconOffsetY = -5;
            end

            if self.gridData.need_shadow == 1 or ItemM.query(itemId, "float_in_grid") == 1 then
                -- 需要漂浮的要特殊处理
                self.hasShadow = true;

                if icon == nil then
                    -- 直接使用道具图标做漂浮效果，图标需要上移
                    self.offsetByY = true;
                end
            end

            -- 活动副本层
            if ActivityDungeonM.isInActivityBossLayer() then
                if "table" == type(self.gridData.icon_offset) then
                    iconOffset = self.gridData.icon_offset;
                end
                -- 不要阴影
                self.hideShadow = false;
            end

            -- 物品根据配置是否闪光
            if ItemM.query(itemId, "shinning") == 1
               or self.gridData.need_shining == 1 then
                self.needShining = true;
            end

        elseif self.gridData.bonus[1] == 2 then
            -- 属性奖励
            local icon = self.gridData.icon;
            if not icon or
                (SkyInstanceM.isInSkyInstance() and
                 SkyInstanceM.isFirstRewardsTaken(self.gridData)) then
                if self.gridData.bonus[2] == "search_point" then
                    icon = "search_point1";
                else
                    icon = FieldsM.query(self.gridData.bonus[2], "icon");
                end
            end

            iconPath = getLevelItemIconPath(icon);

            if self.gridData.bonus[2] == "gem" then
                -- 钻石
                self.needShining = true;
                self.hasShadow = true;
            end

            -- 观众的掉落和观众一起在初始化时need_shadow被配置为1,但是策划需求钱袋不漂浮，所以这里判断下
            if (self.gridData.need_shadow == 1 and self.gridData.bonus[2] ~= "money") or
                (self.gridData.need_shadow == 1 and self.gridData.bonus[2] == "money" and
                not ArenaM.isInArena() and not ActivityDungeonM.isInActivityBossLayer()) then
                self.hasShadow = true;
            end

            if self.gridData.need_shining == 1 then
                self.needShining = true;
            end
        end
    end

    self.icon = ccui.ImageView:create(iconPath);
    self.icon:setName("icon");
    self.icon:setTouchEnabled(true);
    AddTouchEventListener(self.icon, onClicked, true);
    if iconOffset then
        self.item:setPositionX(self.item:getPositionX() + iconOffset[1]);
        self.item:setPositionY(self.item:getPositionY() + iconOffset[2]);
    end

    if self.hasShadow then
        self.icon:setPositionY(5);
        if self:isKeyGrid() then
            self.icon:setPositionY(0);
        end

        if not self.hideShadow then
            self.itemShadow = ccui.ImageView:create(getLevelItemIconPath("shadow"));
            self.itemShadow:setPositionY(10);
            self.item:addChild(self.itemShadow);
        end
    end

    if self.needShining then
        shiningEffect(self.icon, self.shiningAjustPos.x, self.shiningAjustPos.y);
    end

    self.item:addChild(self.icon);

    if self.gridData.need_prison == 1 then
        -- 需要用监狱图标遮挡
        local prisonIcon = getLevelItemIconPath("prison");
        local prisonImg  = ccui.ImageView:create(prisonIcon);
        prisonImg:setName("prison_icon");
        self.item:addChild(prisonImg);
    end

    self.hasItem = true;

    -- 还原一些初始属性
    self:initGridState();
end

-- 是否已经清除怪物
function UIGrid:isCleared()
    if (self.gridData:isMonster())
        and not self.gridData.monster:isDead() then
        return false;
    end

    return true;
end

-- 播放特效
function UIGrid:playEffect(parent, path, speed, callback, scale, offset)
    Profiler.funcBegin("UIGrid:playEffect");
    parent:setVisible(true);

    local effect, animAction = AnimationCacheM.createAnimationNodeAndAction(path);

    -- 设置速度
    if speed ~= nil then
        animAction:setTimeSpeed(speed);
    end

    -- 设置偏移
    if offset ~= nil then
        effect:setPosition(offset);
    end

    -- 设置缩放
    if scale ~= nil then
        effect:setScale(scale);
    end

    effect:runAction(animAction);
    animAction:gotoFrameAndPlay(1, 60, false);

    local callfunc = cc.CallFunc:create(function()
        parent:removeChild(effect);
        AnimationCacheM.freeAnimationNodeAndAction(path, effect, animAction);
        if callback ~= nil then
            callback();
        end
    end);

    effect:runAction(cc.Sequence:create(cc.DelayTime:create(0.5), callfunc));
    parent:addChild(effect);
    Profiler.funcEnd("UIGrid:playEffect");
end

-- 初始化怪物
function UIGrid:initMonster(firstInit)
    self:cleanMonsterNode();

    if ActivityDungeonM.isInActivityBossLayer() then
        local summonMonster = ME.user.chessBossAppearSummon;
        if type(summonMonster) == "table"
            and table.indexOf(summonMonster, self.gridData:getPos()) >= 0 then
            return;
        end
    end

    if self.icon then
        -- self.icon:setTouchEnabled(false);
        -- self.icon:setVisible(false);
        self.item:removeChild(self.icon);
        self.icon = nil;
    end

    -- 创建怪物模型
    local monster = self.gridData.monster;
    if not monster then
        return;
    end

    local dead = monster:isDead();
    if firstInit then
        -- 刚进迷宫时创建格子，如果怪物已死亡(比如暂离回来)，就不需要创建模型
        if not dead then
            self:createMonster();
        end
    else
        -- 二次创建时，比如(帐篷刷怪)，怪可能一出现就被杀死，但此处仍旧创建模型，由后续流程将其删除或隐藏
        self:createMonster();
    end

    -- 如果是乱入的资源怪，那么先隐藏
    local class = self.gridData.class;
    if (DungeonM.query(class, "rule") == "intrude_monster"
        or DungeonM.query(class, "rule") == "intrude_monster2")
        and not dead then
        self:setVisible(false);
        self.monsterNode:setVisible(false);
    end

    -- 怪物攻击
    self.attack = self.gridData.attack;

    if self:isKeyGrid() then
        self.hasKey = 1;
        self:initItem(self.gridData);
        self.item:setVisible(false);
    end

    -- 怪物掉落
    if (self.gridData.bonus) then
        self:initItem(self.gridData);
        self.item:setVisible(false);

        if dead then
            self:itemAppear();
        end
    end

    -- 需要显示精英怪标示
    local eliteIcon = findChildByName(self.barNode, "crown");
    if self.gridData.is_elite == 1 then
        eliteIcon:setVisible(true);
    else
        eliteIcon:setVisible(false);
    end

    if self.gridData.type ~= GRID_TYPE_BOX then
        -- 隐藏图标
        local boxIcon = findChildByName(self.barNode, "box_icon");
        boxIcon:setVisible(false);
    end

    self.hasMonster = true;
end

-- 切换背景框、血条等可见状态
function UIGrid:switchBGNode(visable)
    self.monsterNode:setVisible(visable);
    self.monsterBgNode:setVisible(visable);
    self.barNode:setVisible(visable);
end

-- 一些必要的初始化还原
function UIGrid:initGridState()
    self.icon:setOpacity(255);
    self.item:setOpacity(255);
    self.icon:setScale(1.0);
    self.item:setScale(1.0);
end

-- 图腾
function UIGrid:initTotem()
    self:switchBGNode(false);

    local grid = self.gridData;
    local summon = grid.summon;
    local class = grid.class;
    local buildingInfo = BuildingBonusM.queryBuildingView(class);

    if not buildingInfo then
        error("获取不到建筑查看配置，元素类型：" .. class);
        return;
    end

    local function onClicked(sender, eventType)
        if eventType == ccui.TouchEventType.began then
            if self.gridData.state == GRID_STATE_DISABLE then
                -- 已经失效，不处理
                return;
            end

            self:openBuildingView(nil, 0, SummonM.query(summon.classId, "desc"), nil,
                SummonM.query(summon.classId, "name"),
                getLevelItemIconPath(SummonM.query(summon.classId, "icon")));

            -- 音效
            AudioM.playFx("ui_open1");
        end
    end

    -- 先清空下
    self.item:removeAllChildren();
    self.item:stopAllActions();

    -- 建筑图标
    local iconPath = getLevelItemIconPath(SummonM.query(summon.classId, "icon"));

    self.icon = ccui.ImageView:create(iconPath);
    self.icon:setTouchEnabled(true);
    self.icon:addTouchEventListener(onClicked);
    self.item:addChild(self.icon);
    self.iconPath = iconPath;
    self.item:setVisible(true);

    -- 需要把倒计时显示出来，但是血量、攻击需要隐藏
    self.barNode:setVisible(true);
    local hpBarNode = self.hpBarNode;
    local atkBarNode = self.atkBarNode;
    local cdNode = findChildByName(self.barNode, "cd_node");
    local cdLabel = findChildByName(cdNode, "cd");
    cdLabel:setVisible(false);

    --图腾需要隐藏血条攻击条，并显示消失倒计时
    hpBarNode:setVisible(false);
    atkBarNode:setVisible(false);
    cdNode:setVisible(true);

    -- 宝箱图标隐藏
    local boxIcon = findChildByName(self.barNode, "box_icon");
    boxIcon:setVisible(false);

    -- 精英怪标示隐藏
    local eliteIcon = findChildByName(self.barNode, "crown");
    eliteIcon:setVisible(false);

    self:updateCountDown();

    -- 下面这一票只是为了还原到初始状态
    self:initGridState();

    self.offsetByY = false;
    self.hasShadow = false;
end

-- 初始化召唤兽
function UIGrid:initSummon()
    -- 去除暂停
    self.isPause = false;
    self.icon = nil;

    -- 先清除格子上的item
    self:cleanItem();

    -- 如果召唤兽已经死亡了
    if self.gridData.state == GRID_STATE_DISABLE then
        if self.gridData.bonus then
            self:initItem();
            self:itemAppear();
        end

        return;
    end

    -- 隐藏boss的背景框
    self:hideBossFrame();

    if self.gridData.summon:query("totem") == 1 then
        -- performWithDelay(self, function() self:initTotem(); end, 1.3);
        self:initTotem();
        return;
    end

    -- 创建召唤兽模型
    self:createSummon();

    -- 怪物攻击
    self.attack = self.gridData.attack;

    self.hasMonster = true;

    -- 注：这里需要重新设置一下血条和攻击条的位置和大小，因为之前格子可能是boss
    local hpNode  = findChildByName(self.barNode, "hp_node");
    local atkNode = findChildByName(self.barNode, "atk_node");
    local hpImg   = findChildByName(hpNode,"hp_bar");
    local atkImg  = findChildByName(atkNode, "atk_bar");
    hpNode:setPosition(cc.p(-50, 80));
    atkNode:setPosition(cc.p(-50, 5));
    hpImg:loadTexture(getLevelItemIconPath("hp_bar"));
    atkImg:loadTexture(getLevelItemIconPath("atk_bar"));

    -- 更新血量和攻击
    self:updateSummonHPAndAttack();
    -- 更新自动攻击轮次倒计时
    self:updateCountDown();
    -- 绘制主动技能气泡
    self:redrawSummonSkill();
end

-- 初始化信徒
function UIGrid:initBeliever()
    self.item:removeAllChildren();

    -- 冈布奥学徒图标
    local apprenticeIcon = getLevelItemIconPath("apprentice2");
    self.icon = ccui.ImageView:create(apprenticeIcon);
    self.icon:setTouchEnabled(true);
    self.icon:setPositionY(5);
    self.item:addChild(self.icon);

    -- 呼吸
    self.needAnimation = true;
end

-- 初始化功能引导道具
function UIGrid:initFeatureItem()
    -- 查看引导道具
    local function callFunc()

        self:onPickupFeatureItem();

        local function showDesc()
            -- 先检查下数据是否合法
            local classId = self.gridData.class;
            if FeatureItemM.query(classId, "pop_desc") then
                local uiForm = UIFeatureDesc.create(classId, self.index);
                UIMgr.getCurrentScene():addForm(uiForm);
            end
        end

        performWithDelay(self, showDesc, 2);

        EventMgr.fire(event.PLAYER_MOVE, self.gridData:getPos());
    end

    self:initCommonBuilding(callFunc);
end

-- 是否钥匙格
function UIGrid:isKeyGrid()
    if DungeonM.isDoorOpened() then
        -- 门已经开了，说明钥匙已经被拾取
        return false;
    end

    return self.gridData.has_key == 1 or self.hasKey == 1;
end

-- 初始化空格
function UIGrid:initEmpty()
    if self:isKeyGrid() then
        self.hasKey = 1;
        self:initItem(self.gridData);
        self.item:setVisible(false);
    end
end

-- 显示怪物标记
function UIGrid:showMonsterMark(switch, fileName)
    local grid = self.gridData;

    if grid:isOpened() and grid:isMonster() and
        grid.monster.dbase:query("show_key") == 1 and not (grid.is_elite == 1) then
        -- 需要显示钥匙怪标示(带皇冠的精英怪不需要显示)
        local keyIcon = findChildByName(self.barNode, "key");
        keyIcon:setVisible(true);
    end

    -- 如果已经翻开了
    if switch and grid:isOpened() then
        return;
    end

    if switch then
        if grid:isMonster() and grid.monster.dbase:query("show_key") == 1 then
            -- 钥匙怪
            fileName = fileName or "mark_key";
        elseif grid:isMonster() and grid.monster.dbase:query("track") then
            -- 赏金猎人
            fileName = fileName or "track";
        elseif grid:isMonster() and grid.monster.dbase:query("dragon") then
            -- 神龙
            fileName = fileName or "dragon";
        elseif grid.MarkMonster then
            -- 普通怪
            fileName = fileName or "pig";
        elseif not fileName then
            return;
        end
    end

    -- 默认隐藏
    if fileName == nil then
        fileName = "flag";
    end
    self.monsterMark:loadTexture(getLevelItemIconPath(fileName));
    self.monsterMark:setVisible(switch);
end

-- 隐藏boss背景框
function UIGrid:hideBossFrame()
    local bossFrame = findChildByName(self.monsterBgNode, "boss_frame");
    local cdNode    = findChildByName(self.node, "cd_node");
    local frameBg1  = findChildByName(self.monsterBgNode, "frame_bg1");
    local frameBg2  = findChildByName(self.monsterBgNode, "frame_bg2");
    local bossPanel = findChildByName(self.monsterBgNode, "boss_panel");
    local buffNode = findChildByName(self.barNode, "buff_node");
    local debuffNode = findChildByName(self.barNode, "debuff_node");

    if bossFrame:isVisible() then
        bossFrame:setVisible(false);
    end

    if cdNode:isVisible() then
        cdNode:setVisible(false);
    end

    if frameBg1:isVisible() then
        frameBg1:setVisible(false);
    end

    if frameBg2:isVisible() then
        frameBg2:setVisible(false);
    end

    if bossPanel:isVisible() then
        bossPanel:setVisible(false);
    end

    if buffNode:isVisible() then
        buffNode:setVisible(false);
    end

    if debuffNode:isVisible() then
        debuffNode:setVisible(false);
    end
end

-- 销毁怪物模型
function UIGrid:destroyMonsterDisplay()
    Profiler.funcBegin("destroyMonsterDisplay");
    if self.monsterShadow then
        self.monsterNode:removeChild(self.monsterShadow);
        self.monsterShadow = nil;
    end

    if self.monsterNode.monsterDisplay then
        if self.gridData.monster then
            if self.gridData.monster:isAudience() then
                -- 如果是观众怪，移除相关图标
                if self.audienceImg then
                    self.audienceImg:removeFromParent();
                    self.audienceImg = nil;
                end
            else
                Profiler.funcBegin("removeNormalAnimation");
                self.monsterNode.monsterDisplay:removeNormalAnimation();
                Profiler.funcEnd("removeNormalAnimation");
            end
        else
            -- 直接移除相关的动画
            Profiler.funcBegin("removeNormalAnimation");
            self.monsterNode.monsterDisplay:removeNormalAnimation();
            Profiler.funcEnd("removeNormalAnimation");
        end

        -- 记录一下
        self.monsterNode.last_class_id = self.monsterNode.monsterDisplay.classId;

        self.monsterNode.monsterDisplay = nil;
    end

    self.bineSkillNode:setVisible(false);

    -- 隐藏能量条
    self.energy:setVisible(false);
    Profiler.funcEnd("destroyMonsterDisplay");
end

-- 销毁伴生物的模型
function UIGrid:destroyAccompanyDisplay()
    Profiler.funcBegin("destroyAccompanyDisplay");

    -- 直接移除相关的动画
    if self.monsterNode.accompanyDisplay then
        -- 记录一下
        local classId = AccompanyDisplayM.query(self.monsterNode.accompanyDisplay.id, "class_id");
        self.monsterNode.last_accompany_class_id = classId;

        self.monsterNode.accompanyDisplay:removeNormalAnimation();
        self.monsterNode.accompanyDisplay = nil;
    end
    Profiler.funcEnd("destroyAccompanyDisplay");
end

-- 销毁召唤兽模型
function UIGrid:destroySummonDisplay()
    Profiler.funcBegin("destroySummonDisplay");

    if self.monsterShadow then
        self.monsterNode:removeChild(self.monsterShadow);
        self.monsterShadow = nil;
    end

    -- 直接移除相关的动画
    if self.monsterNode.summonDisplay then
        self.monsterNode.summonDisplay:removeNormalAnimation();
        self.monsterNode.summonDisplay = nil;
    end
    Profiler.funcEnd("destroySummonDisplay");
end

-- 获取倒计时
function UIGrid:getCd()
    if self.gridData.monster then
        if self.gridData.type == GRID_TYPE_GHOST_SHIP then
            -- 幽灵船怪
            return GhostShipM.getAttackCountDown(self.gridData.monster);
        elseif self.gridData.monster:isSlime() then
            -- 冈布奥怪
            return ArenaM.getAttackCountDown(self.gridData.monster);
        elseif ActivityBossM.isSpecialBoss(self.gridData.monster) then
            -- 活动副本冈布奥BOSS
            local cd = ActivityBossM.getAttackCountDown(self.gridData.monster);
            if cd then
                return cd;
            end
        end

        -- 怪物
        local cd = self.gridData.monster:getCountDown();
        return cd;
    elseif self.gridData.summon then
        -- 召唤物
        return self.gridData.summon:getCountDown();
    elseif self.gridData.type == GRID_TYPE_INSTANCE then
        -- 流浪者帐篷副本
        local instanceId = self.gridData.instanceId;
        if DungeonTentM.isTentInstance(instanceId) then
            -- 如果在boss层，去boss层的模块取数据
            if DungeonBossM.isInBossLayer() then
                return BossTentM.getRefreshCountDown(self.gridData:getPos());
            else
                return DungeonTentM.getRefreshCountDown();
            end
        end
    elseif self.gridData.type == GRID_TYPE_GARDEN then
        -- 魔藤
        local bine = ME.user.bine;
        if bine then
           return BineM.getAttackCountDown();
        end
    elseif self.gridData.type == GRID_TYPE_GHOST_SHIP then
        -- 幽灵船
        local ship = ME.user.ship;
        if ship then
            return GhostShipM.getAttackCountDown(ship);
        end
    elseif self.gridData.type == GRID_TYPE_TREE_HOUSE then
        -- 树屋
        local treeHouse = ME.user.tree_house;
        if treeHouse then
            return TreeHouseM.getAttackCountDown(treeHouse);
        end
    elseif self.gridData.type == GRID_TYPE_MONSTER_BUILDING then
        -- 通用刷怪建筑
        local classId = self.gridData.class;
        return MonsterBuildingM.getRefreshCountDown(classId, self.gridData.state);
    elseif self.gridData.type == GRID_TYPE_DARK_STONE then
        -- 暗之石柱
        return DarkStoneM.getBlastCountDown(self.gridData:getPos());
    elseif self.gridData.type == GRID_TYPE_BOSS_STONE then
        -- boss层的光/暗石柱
        return SaintTacticsM.getCdCount(self.gridData:getPos());
    elseif self.gridData.type == GRID_TYPE_BISHOP then
        -- 主教
        return BishopM.getAttackCountDown();
    elseif self.gridData.type == GRID_TYPE_TRAP then
        -- 陷阱
        return CommonTrapM.getTrapCountDown(self.gridData:getPos());
    elseif self.gridData.type == GRID_TYPE_STEAM_MACHINE then
        -- 蒸汽机械
        return SteamMachineM.calcMachineCd(self.gridData.steam_machine);
    elseif self.gridData.type == GRID_TYPE_ENERGY_PLATFORM then
        -- 充能平台
        return ActivityBossM.getEnergyPlatformCd(self.gridData:getPos());
    end

    return nil;
end

-- 更新倒计时
function UIGrid:updateCountDown()
    Profiler.funcBegin("updateCountDown");

    local cd = self:getCd();

    local cdNode;
    if self.gridData.monster or self.gridData.summon then
        cdNode = findChildByName(self.barNode, "cd_node");
    else
        cdNode = findChildByName(self.node, "cd_node");
    end

    if self.gridData.type == GRID_TYPE_SUMMON then
        -- 召唤兽可能有技能图标
        self:redrawSummonSkill();
    end

    if (not cd or cd < 0) and not self.isPause or self.gridData.state == GRID_STATE_DISABLE then
        cdNode:setVisible(false);
        return;
    end

    -- 显示倒计时
    local cdLabel = findChildByName(cdNode, "cd");
    if not self.isPause then
        TextStyleM.setTextStyle(cdLabel, TextStyleM.TEXT_SIZE_TINY, TextStyleM.TEXT_COLOR_LIGHT_GREEN, true);
        cdLabel:setString(tostring(cd));
        local outlineColor = TextStyleM.TEXT_OUTLINE_COLOR_DARK;
        cdLabel:enableOutline(cc.c4b(outlineColor[1], outlineColor[2], outlineColor[3], 255), 2);

        -- 警报
        if cd == 1 then
            cdLabel:setTextColor(TextStyleM.getColor("light_red"));

            local scaleUp = cc.ScaleTo:create(0.3, 1.3);
            local scaleDown = cc.ScaleTo:create(0.5, 1.1);
            cdLabel:runAction(cc.RepeatForever:create(cc.Sequence:create(scaleUp, scaleDown)));
        else
            cdLabel:setScale(1);
            cdLabel:setTextColor(TextStyleM.getColor("light_green"));
            cdLabel:stopAllActions();
        end
    end

    cdLabel:setVisible(true);

    local cdBg = findChildByName(cdNode, "bg");
    local cdBg2 = findChildByName(cdNode, "bg2");
    cdNode:setVisible(true);
    if not cdNode.oriPos then
        cdNode.oriPos = cc.p(cdNode:getPosition());
    end

    if not self.isPause then
        cdBg:setVisible(true);
        cdBg2:setVisible(false);
    else
        cdBg:setVisible(false);
        cdBg2:setVisible(true);
    end

    if self.gridData.type == GRID_TYPE_GHOST_SHIP then
        -- 如果是幽灵船，要稍微修正一下位置
        cdNode:setPositionY(-10);
    elseif self.gridData.monster then
        local classId = self.gridData.monster.classId;
        local dbase = MonsterM.query(classId, "dbase");

        -- 如果是boss，需要改下位置和大小(这边每回合都会重新loadTexture，移到fixHpBarPosition)
        if self.gridData.monster:isBoss() then
                -- 如果配置了cd节点的坐标，直接使用

            -- 如果配置了boss背景框
            if dbase["boss_frame_icon"] then
                 TextStyleM.setTextStyle(cdLabel, TextStyleM.TEXT_SIZE_MIDDLE, TextStyleM.TEXT_COLOR_LIGHT_GREEN, true);
            end
        elseif self.gridData.monster:isSlime() then
            -- 如果是冈布奥怪，要稍微修正一下位置
            cdNode:setPosition(cc.p(70 , -10));
        elseif self.gridData.cd_pos then
            -- 指定了位置
            local cdPos = self.gridData.cd_pos;
            cdNode:setPosition(cc.p(cdPos[1], cdPos[2]));
        end
    elseif self.gridData.type == GRID_TYPE_ENERGY_PLATFORM then
        -- 超导充能平台，位置修正
        cdNode:setPositionY(-30);
    elseif self.gridData.cd_pos then
        -- 指定了位置
        local cdPos = self.gridData.cd_pos;
        cdNode:setPosition(cc.p(cdPos[1], cdPos[2]));
    else
        cdNode:setPosition(cdNode.oriPos);
    end

    -- 检查时间停止效果
    checkTimeCeaseEffectForNode(cdNode);

    Profiler.funcEnd("updateCountDown");
end

-- 更新召唤兽的血量和攻击
function UIGrid:updateSummonHPAndAttack()
    Profiler.funcBegin("updateSummonHPAndAttack");

    local summon = self.gridData.summon;
    if not summon then
        return;
    end

    local hp = summon:getHp();
    if hp < 0 then
        hp = 0;
    end

    local hpLabel = findChildByName(self.barNode, "hp_node/hp");
    local attackLabel = findChildByName(self.barNode, "atk_node/attack");

    -- 显示血量和攻击
    hpLabel:setString(tostring(hp));
    local attack = summon:getAttack();
    attackLabel:setString(tostring(attack));

    self:setBarStyle(hpLabel);
    self:setBarStyle(attackLabel);

    Profiler.funcEnd("updateSummonHPAndAttack");
end

-- 更新怪物血量和攻击
function UIGrid:updateHPAndAttack()
    if self.updateHpAndAttackNode == nil then
        self.updateHpAndAttackNode = cc.Node:create();
        self:addChild(self.updateHpAndAttackNode);
    end

    local function doUpdateHPAndAttack()
        Profiler.funcBegin("updateHPAndAttack");

        local monster = self.gridData.monster;
        if not monster then
            return;
        end

        if not self.gridData:isOpened() then
            return;
        end

        if not DungeonM.getCurrentDungeon() then
            return;
        end

        Profiler.funcBegin("updateHPAndAttack1");

        local hp = monster:getHp();

        -- 增加修正量，当血量超过四位数时，需要向右偏移血条，并适当缩小
        local offset = 0;

        if hp > 999 and not monster:isBoss() then
            offset = 13;
        elseif hp > 99 and not monster:isBoss() then
            offset = 8;
        end

        if hp < 0 then
            hp = 0;

            -- 怪物一出场就死，显示的血量为0，显得不合理，此处修正为最大血量
            if self.gridData.monster:isDead() and CombatM.getRound() - (self.gridData.openRound or 0) <= 1 then
                hp = self.gridData.monster:getMaxHp();
            end
        end

        if hp > 9999 then
            hp = "????";
        end

        Profiler.funcEnd("updateHPAndAttack1");
        Profiler.funcBegin("updateHPAndAttack2");

        -- 血量颜色
        local hpColor = AttribColorM.getAttrColor(monster, "hp");
        if self.hpLabel.lastHpColor ~= hpColor then
            self.hpLabel:setColor(hpColor);
            self.hpLabel.lastHpColor = hpColor;
        end

        -- 攻击力颜色
        local attackColor = AttribColorM.getAttrColor(monster, "attack")
        if self.attackLabel.lastAttackColor ~= attackColor then
            self.attackLabel:setColor(attackColor);
            self.attackLabel.lastAttackColor = attackColor;
        end

        Profiler.funcEnd("updateHPAndAttack2");
        Profiler.funcBegin("updateHPAndAttack3");

        -- 显示血量和攻击
        if self.hpLabel.lastHpVal ~= hp then
            self.hpLabel:setString(tostring(hp));
            self.hpLabel:setPositionX(5 + offset);
            self.hpLabel.lastHpVal = hp;
        end

        local attack = monster:getAttack();

        local offset2 = 0;
        if attack > 99 and not monster:isBoss() then
            offset2 = 8;
        end

        if attack > 999 then
            attack = "???";
        end

        local classId = monster.classId;
        local dbase = MonsterM.query(classId, "dbase");

        if self.gridData.type == GRID_TYPE_GHOST_SHIP
            or dbase["hide_monster_attack"] == 1 then
            -- 幽灵船怪或者需要隐藏攻击的怪，不显示攻击
            self.atkBarNode:setVisible(false);
        end
        if dbase["hide_monster_hp"] == 1 then
            -- 需要隐藏血量的怪，不显示血量
            self.hpBarNode:setVisible(false);
        end

        if self.attackLabel.lastAttackVal ~= attack then
            self.attackLabel:setString(tostring(attack));
            self.attackLabel:setPositionX(5 + offset2);
            self.attackLabel.lastAttackVal = attack;
        end

        Profiler.funcEnd("updateHPAndAttack3");

        Profiler.funcEnd("updateHPAndAttack");
    end

    self.updateHpAndAttackNode:stopAllActions();
    performWithDelay(self.updateHpAndAttackNode, doUpdateHPAndAttack, 0.05);
end

-- 显示怪物掉血
function UIGrid:showHpDamaged(damage)
    Profiler.funcBegin("showHpDamaged");
    local hpImg = findChildByName(self.node, "bar_node/hp_node/hp_bar");

    local damageNode = hpImg:getChildByName("damage");

    -- 飘字
    local text = damageNode:clone();
    text:setVisible(true);
    text:setString("." .. damage);
    text:setPosition(cc.p(75, 20));
    local callfunc = cc.CallFunc:create(function()
        hpImg:removeChild(text);
    end);

    -- 第一行略微下调
    if self.index < 6 then
        text:setPositionY(text:getPositionY() - 45);
    end

    local move = cc.JumpBy:create(0.5, cc.p(50, 40), 60, 1);
    local delay = cc.DelayTime:create(0.19);
    local fadeOut = cc.FadeOut:create(0.01);
    local action = cc.Sequence:create(move, callfunc);
    text:runAction(action);

    hpImg:addChild(text);
    Profiler.funcEnd("showHpDamaged");
end

-- 怪物逃跑了
function UIGrid:onMonsterFlee()
    Profiler.funcBegin("onMonsterFlee");

    local grid = self.gridData;
    if not grid:isMonster() or
       grid.state ~= GRID_STATE_DISABLE then
        return;
    end

    -- 销毁怪物模型
    self.hasMonster = false;
    self:destroyMonsterDisplay();

    -- 销毁伴生物模型
    self:destroyAccompanyDisplay();

    -- 隐藏怪物背景图和血条
    self.monsterBgNode:setVisible(false);
    self.barNode:setVisible(false);
    local cdNode = findChildByName(self.barNode, "cd_node");
    cdNode:setVisible(false);

    -- 播放逃跑效果
    playMonsterFleeEffect(self);

    -- 逃跑发言
    local classId = grid.monster.classId;
    local dbase = MonsterM.query(classId, "dbase");
    if dbase["do_flee_talk"] == 1 then
        local monsterName = MonsterM.query(classId, "name");
        alert(string.format(getLocStr("monster_flee"), monsterName));
    end

    -- 播放逃跑音效
    local base_class_id = MonsterM.query(classId, "base_class_id")
    local audio = getLocStr(string.format("id_%s_disapp", base_class_id))
    local path = AudioM.getPath(audio);
    if path ~= nil then
        AudioM.playFx(audio);
    end

    Profiler.funcEnd("onMonsterFlee");
end

-- 逻辑层怪物死亡
function UIGrid:onLogicMonsterDie()
end

-- 怪物死亡
function UIGrid:onMonsterDie()
    Profiler.funcBegin("onMonsterDie");

    -- 清除状态光效
    removeCombatStatusEffect(self, nil, true);

    local function hideBgNode(force)
        if not force and
            (self.monsterNode.monsterDisplay or
            self.monsterNode.summonDisplay or
            self.monsterNode.bishopDisplay or
            self.monsterNode.bineDisplay or
            self.monsterNode.shipDisplay) then
            -- 有模型，那就不管
            return;
        end

        if not force and SummonM.isTotem(self.gridData)
            and self.gridData.state ~= GRID_STATE_DISABLE then
            -- 是图腾
            return;
        end

        -- 隐藏怪物背景图和血条
        self.monsterBgNode:setVisible(false);
        self.barNode:setVisible(false);
        cdNode = findChildByName(self.node, "cd_node");
        cdNode:setVisible(false);

        -- 重置上次血量和缓存数据
        self.hpLabel.lastHpColor = nil;
        self.attackLabel.lastAttackColor = nil;
        self.hpLabel.lastHpVal = nil;
        self.attackLabel.lastAttackVal = nil;

        if self.hasKey == 1 then
            self:gotoOpen(true);
            keyAppearEffect(self.icon, 80, 75);
        end
    end

    local monster = self.gridData.monster;
    if not monster then
        -- 已经不是怪物了，可能格子已经刷成其他类型了
        if self.gridData.summon then
            -- 已经转换成召唤怪物了

            -- 销毁原来的怪物模型
            self:destroyMonsterDisplay();
            hideBgNode();
            return;
        end

        -- 销毁怪物模型
        self.hasMonster = false;
        self:destroyMonsterDisplay();

        local cdNode = findChildByName(self.barNode, "cd_node");
        cdNode:setVisible(false);

        if self.isPause then
            -- 去除暂停
            self.isPause = false;

            -- 将沙漏的背景复原
            local cdBg = findChildByName(cdNode, "bg");
            local cdBg2 = findChildByName(cdNode, "bg2");
            cdBg2:setVisible(false);
            cdBg:setVisible(true);
        end

        hideBgNode(true);

        return;
    end

    if not monster:isDead() then
        -- 怪物没死？可能是格子又刷出了新的怪物
        return;
    end

    -- 标记格子已经没怪
    self.hasMonster = false;

    -- 销毁怪物模型
    self:destroyMonsterDisplay();

    -- 销毁伴生物模型
    self:destroyAccompanyDisplay();

    -- 播放死亡动作
    self:switchMonsterAction(MONSTER_ACTION_DIE);

    local function destroy()
        local monster = self.gridData.monster;
        if monster and not monster:isDead() then
            -- 怪物没死？可能是格子由刷出了新的怪物
            return;
        end

        self.monsterNode:removeChild(self.monsterNode.monsterDisplay);
        self.monsterNode.monsterDisplay = nil;

        if self.monsterNode.waterEffectNode then
            self.monsterNode:removeChild(self.monsterNode.waterEffectNode);
            self.monsterNode.waterEffectNode = nil;
        end
    end

    -- 等死亡动作播完后再销毁
    performWithDelay(self, destroy, 0.3);

    -- 隐藏怪物背景图和血条
    self.monsterBgNode:setVisible(false);
    self.barNode:setVisible(false);
    local cdNode = findChildByName(self.barNode, "cd_node");
    cdNode:setVisible(false);

    if self.isPause then
        -- 去除暂停
        self.isPause = false;

        -- 将沙漏的背景复原
        local cdBg = findChildByName(cdNode, "bg");
        local cdBg2 = findChildByName(cdNode, "bg2");
        cdBg2:setVisible(false);
        cdBg:setVisible(true);
    end

    -- 道具出现
    if self.gridData.bonus then
        self:initItem(self.gridData);
    end
    self:itemAppear();

    if self.hasKey == 1 then
        -- 隐藏钥匙怪提示节点
        local keyNode = findChildByName(self.barNode, "key");
        keyNode:setVisible(false);
        if monster then
            monster.dbase:delete("show_key");
        end

        self:gotoOpen(true);
        keyAppearEffect(self.icon, 80, 75);
    end

    -- 如果当前正在发言，则停止发言
    if self.isTalking then
        self:stopTalking();
    end

    Profiler.funcEnd("onMonsterDie");
end

-- 召唤兽死亡
-- 如果有传入的summonId，那么用传入的summonId
function UIGrid:onSummonDie(summonId)
    Profiler.funcBegin("onSummonDie");

    -- 标记格子已经没怪
    self.hasMonster = false;

    local function hideBgNode(force)
        if not force and
            (self.monsterNode.monsterDisplay or
            self.monsterNode.summonDisplay or
            self.monsterNode.bishopDisplay or
            self.monsterNode.bineDisplay or
            self.monsterNode.shipDisplay) then
            -- 有模型，那就不管
            return;
        end

        -- 隐藏怪物背景图和血条
        self.monsterBgNode:setVisible(false);
        self.barNode:setVisible(false);
        local cdNode = findChildByName(self.barNode, "cd_node");
        cdNode:setVisible(false);
        -- 技能图标
        self:redrawSummonSkill();

        -- 重置上次血量和缓存数据
        self.hpLabel.lastHpColor = nil;
        self.attackLabel.lastAttackColor = nil;
        self.hpLabel.lastHpVal = nil;
        self.attackLabel.lastAttackVal = nil;

        if self.hasItem then
            self:itemAppear();
        end
    end

    if self.gridData.type ~= GRID_TYPE_SUMMON then
        -- 销毁召唤兽模型
        self:destroySummonDisplay();
        -- 隐藏怪物背景图和血条
        hideBgNode();
        return;
    end

    -- 播放死亡动画
    local classId = summonId or self.gridData.summon.classId;
    local dieAnimFormula = SummonM.query(classId, "die_anima_formula");
    local startFrameIndex, endFrameIndex;
    if dieAnimFormula ~= nil and dieAnimFormula ~= "" then
        startFrameIndex, endFrameIndex = FormulaM.invoke(dieAnimFormula);
    end

    if dieAnimFormula == "" or (startFrameIndex == 0 and endFrameIndex == 0) then
        -- 销毁召唤兽模型
        self:destroySummonDisplay();

        -- 使用默认的死亡动画
        local path = "animation/die_bomb.csb";
        local effect, animAction;
        effect, animAction = AnimationCacheM.createAnimationNodeAndAction(path);
        effect:runAction(animAction);
        self:addChild(effect);
        animAction:gotoFrameAndPlay(0, 14, false);

        local function callFunc()
            self:removeChild(effect);
            AnimationCacheM.freeAnimationNodeAndAction(path, effect, animAction);
        end

        if table.indexOf({16,17,18,19}, classId) > 0 then
            AudioM.playFx("death_effects");
        end

        performWithDelay(self, callFunc, 0.3);
    else
        local function callback()
            -- 销毁召唤兽模型
            self:destroySummonDisplay();
        end

        if self.monsterNode.summonDisplay then
            self.monsterNode.summonDisplay:play(startFrameIndex, endFrameIndex, false, 0.5, callback);
        end
    end

    -- 如果有额外动画需要播放
    local effectFun = FormulaM.invoke("SUMMON_DIED_EFFECT", classId, self);
    if effectFun and type(effectFun) == "function" and not self.summonDieEffect then
        -- 避免重复调用
        self.summonDieEffect = true;
        effectFun();
    end

    -- 隐藏怪物背景图和血条
    hideBgNode(true);

    Profiler.funcEnd("onSummonDie");
end

-- 怪物淡入效果
function UIGrid:monsterFadeIn()
    -- 怪物淡入效果
    local fadeIn = cc.FadeIn:create(2);
    self.monsterNode:runAction(fadeIn);
end

-- 怪物刷出
function UIGrid:onRefreshMonster(pos, desc)
    local dungeonData = DungeonM.getCurrentDungeon();

    -- 如果原来的格子有钥匙，需要保留
    self.hasKey = self.gridData.has_key;
    self.gridData = dungeonData[pos];

    if not self.gridData.monster then
        return;
    end

    self.item:removeAllChildren();
    self:initMonster();

    -- 提示怪物刷出
    local classId = self.gridData.monster.classId;
    local monsterName = MonsterM.query(classId, "name");

    if desc == "tent" then
        local grid = DungeonTentM.findTentGrid();
        local msg = string.format(getLocStr("tent_refresh_monster"), monsterName);

        local dbase;
        if grid and grid.instanceId then
            local instanceInfo = DungeonInstanceM.getInstanceInfo(grid.instanceId);
            msg = string.format(getLocStr("monster_come_out_from"), monsterName, instanceInfo["name"] or "");
            dbase = instanceInfo["dbase"];
        end

        alert(msg);

        local talkId;
        if type(dbase) == "table" and tonumber(dbase["talk_fetch_group"]) > 0 then
            -- 指定了对话组别
            talkId = TalkTemplateM.getTalkId(GRID_TYPE_MONSTER, tonumber(dbase["talk_fetch_group"]));
        end

        -- 怪物发言
        self:doRoleTalk(nil, nil, talkId);
    else
        -- 出场发言
        self:showAnimationTalk(MONSTER_ACTION_APPEAR);
    end
end

-- 新格子刷出
function UIGrid:onTransformGrid(pos, desc, effectId)
    -- 先清理一遍格子
    if self.item.isRemoving then
        -- itemNode已经处于清理状态，在这里直接清除（状态设置参见playBuildingDisappear）
        self.item:removeAllChildren();
        self.item.isRemoving = false;
    end

    -- 初始化格子
    self:initByType();

    self.item:setOpacity(255);

    -- 标记格子转换完毕
    self.transforming = false;

    -- 下面这些类型的格子无需在此处播放动画
    local idleList = { GRID_TYPE_DEVELOPER_BOARD, GRID_TYPE_ODD_ALTAR };
    if table.indexOf(idleList, self.gridData.type) ~= -1 then
        return;
    end

    -- 播放出现动画
    if self.gridData.monster then
        -- 如果是怪物，根据配置播放怪物出现动画
        local classId = self.gridData.monster.classId;
        local dbase = MonsterM.query(classId, "dbase");
        local appearType = dbase["appear_type"];
        if appearType == "steam_boss_seccond" then
            -- 蒸汽之都boss第二阶段
            self:setVisible(true);
        else
            -- 默认效果
            playMonsterAppearEffect(self, self.monsterNode, self.monsterBgNode, self.barNode, effectId);
        end
    elseif self.gridData.summon then
        -- 是召唤兽
        -- 如果配置了出场特效，则播放
        local classId = self.gridData.summon.classId;
        local effectId = SummonM.query(classId, "appear_effect");
        if type(effectId) == 'number' then
            playSummonAppearEffect(self, effectId);
        end

    elseif self.gridData.type == GRID_TYPE_COFFIN then
        if self.item:isVisible() ~= true then
            self.item:setVisible(true);
        end

        local elementDbase = DungeonM.queryElement(self.gridData.element, "dbase");
        if elementDbase["no_appear_effect"] ~= 1 then
            -- 圣棺
            onCoffinAppear(self);
        end

    elseif self.icon then
        if self.item:isVisible() ~= true then
            self.item:setVisible(true);
        end
        local scaleUp = cc.ScaleTo:create(0.25, 1.2);
        local scaleDown = cc.ScaleTo:create(0.15, 1);
        self.icon:runAction(cc.Sequence:create(scaleUp, scaleDown));

    elseif self.gridData.type == GRID_TYPE_SLIME_EVENT then
        -- 冈布奥触发事件
        if self.mammothEffect then
            playBuildingStrongLight(self);
        end

    end

    local uiLevel = UIDungeonMgr.getCurLevel();
    -- 设置时间停止需要重刷
    if uiLevel then
        uiLevel:setTimeCeaseNeedRefresh(true);
    end
end

-- 创建观众怪模型
function UIGrid:createAudienceDisplay(classId)
    if MonsterM.query(classId, "monster_type") ~= MONSTER_TYPE_AUDIENCE then
        trace("UIGrid", "怪物%s不是观众怪，无法创建观众模型。", tonumber(classId));
        return;
    end

    local icon;
    local childIcon;
    local childOffset;
    local offsetY = 0;
    local offsetX = 0;
    local scale = 1.0;
    local audienceType;
    local needBreath = true;
    if ArenaM.isInArena() then
        -- 角斗场
        local audienceType = ArenaM.queryAudience(classId, "audience_type");
        if audienceType == AUDIENCE_TYPE_APPRENTICE then
            -- 学徒
            icon = getLevelItemIconPath("child");
            offsetY = -10;
        elseif audienceType == AUDIENCE_TYPE_VILLAGER_MALE then
            -- 村民男
            icon = getLevelItemIconPath("uncle");
        elseif audienceType == AUDIENCE_TYPE_VILLAGER_FEMALE then
            -- 村民女
            icon = getLevelItemIconPath("aunt");
        elseif audienceType == AUDIENCE_TYPE_ROMAN_MALE then
            -- 罗马男
            icon = getLevelItemIconPath("roman_male");
        elseif audienceType == AUDIENCE_TYPE_ROMAN_FEMALE then
            -- 罗马女
            icon = getLevelItemIconPath("roman_female");
        elseif audienceType == AUDIENCE_TYPE_SLIME then
            -- 冈布奥
            local dbase = ArenaM.queryAudience(classId, "dbase");
            local petId = dbase["pet_id"];
            icon = getHeroIconPath(PetM.getDefaultIcon(petId));
            scale = 1.3;
            offsetY = -20;
        else
            error(string.format("暂不支持的观众类型：%s", tostring(audienceType)));
        end
    else
        -- 通用的观众模块
        local iconPath = NormalAudienceM.queryAudience(classId, "icon");
        icon = getLevelItemIconPath(iconPath);
        local offset = NormalAudienceM.queryAudience(classId, "offset");
        if type(offset) == "table" and NormalAudienceM.needFixPosition() then
            offsetX = offset[1];
            offsetY = offset[2];
        end

        local dbase = NormalAudienceM.queryAudience(classId, "dbase");
        if dbase["breath"] == 1 then
            needBreath = true;
        else
            needBreath = false;
        end

        -- 有子图
        if dbase["child_icon"]then
            childIcon = getLevelItemIconPath(dbase["child_icon"]);
            childOffset = dbase["child_offset"] or {0, 0};
        else
            if self.gridData.child_icon then
                if string.startWith(self.gridData.child_icon, "item(") then
                    local endIndex = string.find(self.gridData.child_icon, ")");
                    local classId = tonumber(string.sub(self.gridData.child_icon, 6, endIndex - 1));
                    childIcon = getItemIconPath(classId);
                    childOffset = self.gridData.child_offset or {0, 0};
                else
                    childIcon = getLevelItemIconPath(self.gridData.child_icon);
                    childOffset = self.gridData.child_offset or {0, 0};
                end
            end
        end

        -- 电磁干扰仪
        if dbase["building_view"] == "interfere_device" then
            -- 电流光效
            local node = cc.Node:create();
            node:setPosition(65, 65);
            playEffect(node, 1338, 0, 50);
            self.interfereEffectNode = node;
        end
    end

    local iconImg = ccui.ImageView:create(icon);
    self.audienceImg = iconImg;
    iconImg:setPositionX(offsetX);
    iconImg:setPositionY(offsetY);
    iconImg:setScale(scale);

    if self.interfereEffectNode then
        iconImg:addChild(self.interfereEffectNode);
        self.interfereEffectNode = nil;
    end

    if childIcon then
        -- 建造上悬浮的道具
        local iconChild = ccui.ImageView:create(childIcon);
        iconChild:setPositionX(childOffset[1]);
        iconChild:setPositionY(childOffset[2]);
        iconImg:addChild(iconChild);

        -- 道具的蒙版
        if self.gridData.child_mask then
            local maskImg = ccui.ImageView:create(getLevelItemIconPath(self.gridData.child_mask));
            maskImg:setPositionX(iconChild:getContentSize().width / 2);
            maskImg:setPositionY(iconChild:getContentSize().height / 2);
            iconChild:addChild(maskImg);
        end

        local oriPosX = iconChild:getPositionX();
        local oriPosY = iconChild:getPositionY();

        -- 道具上下浮动
        local function moveItem()

            local function doItemMove()
                local moveUp = cc.MoveTo:create(1, cc.p(oriPosX, oriPosY + 10));
                local moveDown = cc.MoveTo:create(1, cc.p(oriPosX, oriPosY));
                local function moveDone()
                    if iconChild then
                        performWithDelay(iconChild, doItemMove, 0.2);
                    end
                end

                local callFunc = cc.CallFunc:create(moveDone);
                iconChild:runAction(cc.Sequence:create(moveUp, moveDown, callFunc));
            end

            if iconChild and not iconChild.isMoving then
                iconChild.isMoving = true;
                performWithDelay(iconChild, doItemMove, 0.1);
            end
        end

        if iconChild then
            local delay = math.random(1, 50) / 100;
            performWithDelay(iconChild, moveItem, delay);
        end
    end

    -- 将锚点设置在底部居中，方便播放动画
    if not self.hasSetAnimationPos then
        iconImg:setAnchorPoint(cc.p(0.5, 0));
        iconImg:setPosition(iconImg:getPositionX(),
                            iconImg:getPositionY() - iconImg:getContentSize().height / 2);
        self.hasSetAnimationPos = true;
    end

    -- 呼吸效果
    -- 随机延时后再开始，错开各个观众的呼吸频率
    if needBreath and tolua.cast(self, "cc.Node") ~= nil then
        local delay = math.random(1, 10) / 10;
        performWithDelay(self, function() self:doModelBreath(iconImg); end, delay);
    end

    return iconImg;
end

-- 创建怪物模型
function UIGrid:createMonsterDisplay(classId, para)
    if MonsterM.query(classId, "monster_type") == MONSTER_TYPE_AUDIENCE then
        -- 观众怪
        return self:createAudienceDisplay(classId);
    end

    local monsterDisplay;
    local dbase = MonsterM.query(classId, "dbase");
    local extra;
    if MonsterM.query(classId, "monster_type") == MONSTER_TYPE_SLIME
        or dbase["slime_display"] == 1 then
        local petId;
        -- 冈布奥怪
        if ArenaM.isInArena() then
            -- 竞技场
            petId = ArenaM.querySlime(classId, "pet_id");
        elseif ActivityDungeonM.isInActivityBossLayer() then
            -- 活动副本
            petId = ActivityBossM.queryBoss(classId, "pet_id");
            local exDbase = ActivityBossM.queryBoss(classId, "dbase");
            if exDbase["anim_name"] then
                extra = {["anim_name"] = exDbase["anim_name"]};
            end
        elseif type(dbase["pet_id"]) == "number" then
            petId = dbase["pet_id"];
        else
            -- 上面都不匹配，默认再尝试从竞技场配置中获取petId
            petId = ArenaM.querySlime(classId, "pet_id");
        end

        -- 如果配置了pet_id，则使用冈布奥的模型
        if type(petId) == "number" then
            local modelId = PetM.query(petId, "default_model");
            monsterDisplay = HeroDisplay.create(petId, extra);

            -- 标记这是冈布奥的模型
            monsterDisplay.isSlime = true;
        else
            monsterDisplay = MonsterDisplay.create(classId);
        end
    else
        -- 普通怪
        monsterDisplay = MonsterDisplay.create(classId, para);
    end

    -- 设置缩放比例
    local scale = MonsterM.query(classId, "model_scale");
    monsterDisplay:setScale(scale);

    -- 设置偏移量
    local offsetX = MonsterM.query(classId, "offset_x") or 0;
    local offsetY = MonsterM.query(classId, "offset_y") or 0;
    monsterDisplay:setPosition(tonumber(offsetX), tonumber(offsetY));

    local childList = getAllChilds(monsterDisplay);
    for _, child in pairs(childList) do
        setNormalMode(child);
    end

    return monsterDisplay;
end

-- 创建替换怪物模型
function UIGrid:createExtraMonsterDisplay(classId)
    -- 创建怪物模型
    local monsterDisplay = ExtraMonsterDisplay.create(classId);

    -- 设置缩放比例
    local scale = ExtraMonsterM.query(classId, "model_scale");
    monsterDisplay:setScale(scale);

    -- 设置偏移量
    local offsetX = ExtraMonsterM.query(classId, "offset_x") or 0;
    local offsetY = ExtraMonsterM.query(classId, "offset_y") or 0;
    monsterDisplay:setPosition(tonumber(offsetX), tonumber(offsetY));

    local childList = getAllChilds(monsterDisplay);
    for _, child in pairs(childList) do
        setNormalMode(child);
    end

    return monsterDisplay;
end

-- 创建伴生物模型
function UIGrid:createAccompanyDisplay(id)
    -- 创建怪物模型
    local monsterDisplay = AccompanyDisplay.create(id);

    -- 设置缩放比例
    local scale = AccompanyDisplayM.query(id, "model_scale");
    monsterDisplay:setScale(scale);

    -- 设置偏移量
    local offsetX = AccompanyDisplayM.query(id, "offset_x") or 0;
    local offsetY = AccompanyDisplayM.query(id, "offset_y") or 0;
    monsterDisplay:setPosition(tonumber(offsetX), tonumber(offsetY));

    return monsterDisplay;
end

-- 创建召唤兽模型
function UIGrid:createSummonDisplay(classId)
    if self.monsterNode.monsterDisplay then
        -- 先移除旧模型
        self.monsterNode:removeChild(self.monsterNode.monsterDisplay);
        self.monsterNode.monsterDisplay = nil;
    end
    if self.monsterNode.summonDisplay then
        -- 先移除旧模型
        self.monsterNode:removeChild(self.monsterNode.summonDisplay);
        self.monsterNode.summonDisplay = nil;
    end

    -- 创建模型
    local summonDisplay = SummonDisplay.create(classId);

    -- 设置缩放比例
    local scale = SummonM.query(classId, "model_scale");
    summonDisplay:setScale(scale);

    -- 设置偏移量
    local offsetX = SummonM.query(classId, "offset_x") or 0;
    local offsetY = SummonM.query(classId, "offset_y") or 0;
    summonDisplay:setPosition(tonumber(offsetX), tonumber(offsetY));

    local childList = getAllChilds(summonDisplay);
    for _, child in pairs(childList) do
        setNormalMode(child);
    end

    return summonDisplay;
end

-- 修正血条位置
function UIGrid:fixHpBarPos(dbase)
    local hpNode  = findChildByName(self.barNode, "hp_node");
    local atkNode  = findChildByName(self.barNode, "atk_node");
    local hpLabel = findChildByName(hpNode, "hp");
    local attackLabel = findChildByName(atkNode, "attack");
    local hpImg = findChildByName(hpNode, "hp_bar");
    local atkImg = findChildByName(atkNode, "atk_bar");

    -- 调整位置
    local gridWidth  = 128;
    local gridHeight = 130;
    local offsetX = 10;  -- 往右偏移一点，避免血条被盖住

    if dbase["need_pos"] then
        gridWidth  = 70;
        gridHeight = 85;
    end

    local hpBarOffset = dbase["hp_bar_offset"] or { 0, 0, };
    local atkBarOffset = dbase["attack_bar_offset"] or { 0, 0, };

    -- 把偏移量记录在节点上
    hpNode.offsetX = iif(hpNode.offsetX, hpNode.offsetX, hpNode:getPositionX() - gridWidth + offsetX + hpBarOffset[1]);
    hpNode.offsetY = iif(hpNode.offsetY, hpNode.offsetY, hpNode:getPositionY() + gridHeight + hpBarOffset[2]);
    hpNode:setPosition(hpNode.offsetX, hpNode.offsetY);

    atkNode.offsetX = iif(atkNode.offsetX, atkNode.offsetX, atkNode:getPositionX() - gridWidth + offsetX + atkBarOffset[1]);
    atkNode.offsetY = iif(atkNode.offsetY, atkNode.offsetY, atkNode:getPositionY() - gridHeight + atkBarOffset[2]);
    atkNode:setPosition(atkNode.offsetX, atkNode.offsetY);

    hpLabel:setScale(1.0);
    attackLabel:setScale(1.0);
    TextStyleM.setTextStyle(hpLabel, TextStyleM.TEXT_SIZE_MIDDLE, TextStyleM.TEXT_COLOR_WHITE, true, -2);

    -- 血条放大后，攻击文本有点偏上，需要修正一下
    attackLabel:setPosition(cc.pAdd(cc.p(attackLabel:getPosition()), cc.p(2, 0)));
    TextStyleM.setTextStyle(attackLabel, TextStyleM.TEXT_SIZE_MIDDLE, TextStyleM.TEXT_COLOR_WHITE, true, -2);

    -- 替换图片
    hpImg:loadTexture(getLevelItemIconPath("hp3"));
    atkImg:loadTexture(getLevelItemIconPath("atk3"));

    -- 显示沙漏
    local cdNode;
    if self.gridData.monster or self.gridData.summon then
        cdNode = findChildByName(self.barNode, "cd_node");
    else
        cdNode = findChildByName(self.node, "cd_node");
    end
    local cdBg = findChildByName(cdNode, "bg");
    local cdBg2 = findChildByName(cdNode, "bg2");
    local cdLabel = findChildByName(cdNode, "cd");
    if self.gridData.monster:isBoss() then
        -- 如果配置了cd节点的坐标，直接使用
        if type(dbase["cd_pos"]) == "table" then
            cdNode:setPosition(cc.p(dbase["cd_pos"][1], dbase["cd_pos"][2]));
        end

        -- 如果配置了boss背景框
        if dbase["boss_frame_icon"] then
            local cdBg = findChildByName(cdNode, "bg");
            local cdBg2 = findChildByName(cdNode, "bg2");
            cdBg:loadTexture(getLevelItemIconPath("sandglass"));
            cdBg2:loadTexture(getLevelItemIconPath("cold_sandglass"));
            cdNode:setPosition(170, -120);
            TextStyleM.setTextStyle(cdLabel, TextStyleM.TEXT_SIZE_MIDDLE, TextStyleM.TEXT_COLOR_LIGHT_GREEN, true);
        end
    end
end

-- 创建召唤兽
function UIGrid:createSummon()
    if self.monsterNode.monsterDisplay then
        -- 先移除旧模型
        self.monsterNode:removeChild(self.monsterNode.monsterDisplay);
        self.monsterNode.monsterDisplay = nil;
    end

    if self.monsterNode.summonDisplay then
        -- 先移除旧模型
        self.monsterNode:removeChild(self.monsterNode.summonDisplay);
        self.monsterNode.summonDisplay = nil;
    end

    -- 显示底框
    self.monsterBgNode:setVisible(true);

    local monsterFrame = findChildByName(self.monsterBgNode, "monster_frame");
    monsterFrame:setTouchEnabled(true);
    monsterFrame:setVisible(true);

    local classId = self.gridData.summon:query("class_id");
    local summonDisplay = self:createSummonDisplay(classId);
    self.monsterNode.summonDisplay = summonDisplay;
    self.monsterNode:addChild(summonDisplay);
    self.monsterNode:setVisible(false);
    monsterFrame:setOpacity(255);
    local cdNode = findChildByName(self.barNode, "cd_node");
    if cdNode.oriPos then
        cdNode:setPosition(cdNode.oriPos);
    end

    -- 活动副本部分楼层需要隐藏底座
    if ActivityDungeonM.needHideFrame() then
        monsterFrame:setOpacity(0);
        self.monsterShadow = ccui.ImageView:create(getLevelItemIconPath("shadow"));
        self.monsterShadow:setPositionY(10);
        self.monsterShadow:setLocalZOrder(-1);
        self.monsterNode:addChild(self.monsterShadow);
    end

    -- 显示血条和攻击
    self.barNode:setVisible(true);
    self.hpBarNode:setVisible(true);
    self.atkBarNode:setVisible(true);

    -- 注册模型点击事件
    self.isLongPress = false;

    local function onClicked(sender, eventType)
        local function longPressCallback()
            self:showSummonInfo();
        end

        -- 处理长按
        if handleLongPress(sender, eventType, longPressCallback) then
            return;
        end

        if eventType == ccui.TouchEventType.ended then
            if MonsterCaptainM.isMonsterCaptain(self.gridData.summon) then
                -- 海怪船长
                require "game/ui/form/dungeon/special/UIMonsterCaptain";
                local uiInfo = UIMonsterCaptain.create(self.gridData.summon);
                UIMgr.getCurrentScene():addForm(uiInfo);
            end
        end
    end
    monsterFrame:addTouchEventListener(onClicked);

    -- 精英怪标示隐藏
    local eliteIcon = findChildByName(self.barNode, "crown");
    eliteIcon:setVisible(false);

    -- 宝箱图标改为标示召唤兽的图标
    local typeIcon = SummonM.query(classId, "type_icon");
    local iconPath = getSummonTypeIconPath(typeIcon);
    local boxIcon = findChildByName(self.barNode, "box_icon");

    if typeIcon ~= "" then
        boxIcon:setVisible(true);
    end
    boxIcon:loadTexture(iconPath);

    -- 注册技能气泡点击事件
    local dialogBg = findChildByName(self.bineSkillNode, "bg");
    dialogBg:setTouchEnabled(true);
    local function onDialogBgClick(sender, eventType)
        if eventType == ccui.TouchEventType.began then
            local pos = self.gridData:getPos();
            local grid = DungeonM.getGridByPos(pos);
            local summon = grid.summon;

            -- 判断是否死亡
            if not summon or summon:isDead() then
                return;
            end

            local skills = SummonM.getAllSkills(summon) or {};
            if #skills <= 0 then
                -- 没有技能
                return;
            end

            local skillId = skills[1];
            if not SummonM.isMpEnough(summon, skillId) then
                -- 魔法值不足
                alert(getLocStr("no_enough_mp"));
                return;
            end

            -- 音效
            AudioM.playFx("button_click");

            -- 打开施法界面
            require "game/ui/form/dungeon/special/UISummonSelectTarget"
            UIMgr.getCurrentScene():removeFormByName("UISummonSelectTarget");
            local uiForm = UISummonSelectTarget.create(summon, skillId);
            UIMgr.getCurrentScene():addForm(uiForm);
        end
    end
    dialogBg:addTouchEventListener(onDialogBgClick);
end

-- 创建怪物
function UIGrid:createMonster()
    -- 普通怪物
    if self.monsterNode.monsterDisplay then
        -- 先移除旧模型
        self.monsterNode:removeChild(self.monsterNode.monsterDisplay);
        self.monsterNode.monsterDisplay = nil;
    end

    -- 移除伴生物的模型
    if self.monsterNode.accompanyDisplay then
        self.monsterNode:removeChild(self.monsterNode.accompanyDisplay);
        self.monsterNode.accompanyDisplay = nil;
    end

    -- 根据配置，创建伴生物模型
    local classId = self.gridData.monster:query("class_id");
    local dbase = MonsterM.query(classId, "dbase");
    local accompanyId = dbase["accompany_monster_id"];
    if type(accompanyId) == "number" then
        local accompanyDisplay = self:createAccompanyDisplay(accompanyId);

        self.monsterNode.accompanyDisplay = accompanyDisplay;
        self.monsterNode:addChild(accompanyDisplay);
    end

    -- 创建怪物模型
    local monsterDisplay = self:createMonsterDisplay(classId);
    self.monsterNode.monsterDisplay = monsterDisplay;
    self.monsterNode:addChild(monsterDisplay);
    self.monsterNode:setVisible(false);
    self.monsterNode:setScale(1.0);

    self.barNode:setVisible(false);
    local cdNode = findChildByName(self.barNode, "cd_node");
    cdNode:setVisible(false);

    local hpBarNode = self.hpBarNode;
    local atkBarNode = self.atkBarNode;
    local hpLabel = self.hpLabel;
    local attackLabel = self.attackLabel;

    -- 模型偏移
    local offset = dbase["model_offset"];
    if type(offset) == 'number' then
        self.monsterNode:setPositionY(self.monsterNode:getPositionY() + offset);
        self.barNode:setPositionY(self.barNode:getPositionY() + offset);
    end

    -- 需要显示血量和攻击
    hpBarNode:setVisible(true);
    atkBarNode:setVisible(true);

    self:setBarStyle(hpLabel);
    self:setBarStyle(attackLabel);

    self:updateHPAndAttack();
    if self.gridData.monster:isDead() then

    end

    self:updateCountDown();

    self:updateEnergy();
    -- 调整buff和debuff位置
    self:showBuff();

    -- 注册模型点击事件
    self.isLongPress = false;

    local function onClicked(sender, eventType)
        if self.gridData.monster and self.gridData.monster:isAudience() then
            -- 观众怪不支持点击
            if eventType == ccui.TouchEventType.ended then
                -- 可能有建筑界面要显示
                self:audienceBuildingView();
            end
            return;
        end

        local function longPressCallback()
            if not self.gridData.monster then
                return;
            end

            local classId = self.gridData.monster:query("class_id");
            self:showMonsterInfo(classId);
        end

        -- 处理长按
        if handleLongPress(sender, eventType, longPressCallback) then
            return;
        end

        if eventType == ccui.TouchEventType.ended then
            if not self.gridData.monster then
                return;
            end

            local sequence = SkillM.getSequence(self.gridData.monster)
            if sequence ~= nil and ( not sequence:empty() ) then
                -- 上轮攻击未结束
                cclog("正在战斗中");
                --return;
            end

            -- 检测英雄是否还活着
            if ME.user:isDead() then
                return false;
            end

            -- 怪物已死亡
            if self.gridData.monster:isDead() then
                return false;
            end

            -- 需要判断是否能攻击
            if not self.gridData:canHit() then
                alert(getLocStr("cannot_reach"));
                return false;
            end

            -- 判断怪物是否被禁止攻击
            if self.gridData.monster.dbase:query("guide_not_attack_monster") == 1 then
                alert(getLocStr("guide_not_attack_monster"));
                return;
            end

            -- 引导玩家长按
            if self.gridData.monster.dbase:query("guide_longpress_monster") == 1 then
                alert(getLocStr("guide_longpress_monster"));
                return;
            end

            -- 如果是boss，实时更新debuff图标
            if self.gridData.monster and self.gridData.monster:isBoss() then
                self:updateDebuff();
            end

            -- 怪物攻击先在此处模拟
            DungeonActionM.go("physic_attack", self.gridData:getPos());

            EventMgr.fire(event.PLAYER_MOVE, self.index);
            return true;
        end
    end

    local monsterFrame = findChildByName(self.monsterBgNode, "monster_frame");
    local bossFrame = findChildByName(self.monsterBgNode, "boss_frame");
    local frameBg1 = findChildByName(self.monsterBgNode, "frame_bg1");
    frameBg1:setVisible(false);

    local frameBg2 = findChildByName(self.monsterBgNode, "frame_bg2");
    frameBg2:setVisible(false);

    local bossPanel = findChildByName(self.monsterBgNode, "boss_panel");

    -- 如果是boss怪
    local dbase = MonsterM.query(classId, "dbase");
    if self.gridData.monster:isBoss() and not dbase["slime_display"] then
        -- 设置boss的点击范围
        local classId = self.gridData.monster.classId;

        -- 如果配置了boss背景框,或者特殊需求个怪物
        if dbase["boss_frame_icon"] or dbase["need_pos"]
            or dbase["hp_bar_offset"] or dbase["attack_bar_offset"] then
            -- 使用panel响应点击
            bossPanel:setTouchEnabled(true);
            bossPanel:addTouchEventListener(onClicked);
            bossPanel:setVisible(true);

            if dbase["boss_frame_icon"] then
                bossFrame:setVisible(true);
                frameBg1:setVisible(true);
                frameBg2:setVisible(true);
                monsterFrame:setVisible(false);

                bossFrame:loadTexture("images/ui/level/bg/" .. dbase["boss_frame_icon"] .. ".png");
                frameBg1:loadTexture("images/ui/level/bg/boss_frame_bg.png");
                frameBg2:loadTexture("images/ui/level/bg/boss_frame_bg.png");
                frameBg2:setFlippedX(true);
            end

            local range = dbase["boss_click_range"];

            -- 必须配置boss点击范围
            assert(type(range) == "table" and #range == 2, "boss点击范围配置有误");

            local gridWidth = 128;
            local gridHeight = 135;

            bossPanel:setContentSize(range[1] * gridWidth, range[2] * gridHeight);

            -- 根据配置，决定是否裁剪右边
            local bossMonsterNode = findChildByName(self.node, "clip_panel/boss_monster");

            local cutRight = dbase["cut_right"];
            if cutRight == 1 then
                local cutPanel = findChildByName(self.node, "clip_panel");

                cutPanel:setPosition(157, -136);
                bossMonsterNode:setPosition(475, 148);
            end

            -- 需要修正血条位置
            self:fixHpBarPos(dbase);

            -- 修正能量条位置
            --self:fixEnergyPos();

            -- 更新debuff图标
            self:updateDebuff();
        else
            -- 没有配置背景框，点击范围默认为一个格子
            trace("UIGrid", "没有配置boss（%d）的背景框，点击范围默认为一个格子", classId);
            monsterFrame:setTouchEnabled(true);
            monsterFrame:addTouchEventListener(onClicked);
            monsterFrame:setVisible(true);
            bossFrame:setVisible(false);
            frameBg1:setVisible(false);
            frameBg2:setVisible(false);

            if dbase["hide_monster_frame"] == 1 then
                -- 如果需要隐藏背景框，则设置透明度为不可见
                monsterFrame:setOpacity(0);
            else
                monsterFrame:setOpacity(255);
            end

            bossPanel:setVisible(false);

            -- 更新debuff图标
            self:updateDebuff();
        end
    else
        -- 普通怪
        monsterFrame:setTouchEnabled(true);
        AddTouchEventListener(monsterFrame, onClicked);
        monsterFrame:setVisible(true);
        bossFrame:setVisible(false);
        bossPanel:setVisible(false);
        monsterFrame:setOpacity(255);

        -- 冈布奥怪
        if self.gridData.monster:isSlime() or dbase["slime_display"] and dbase["force_show_frame"]  ~= 1 then
            -- 不显示背景框
            monsterFrame:setOpacity(0);

            -- 如果使用的是冈布奥的模型
            if self.monsterNode.monsterDisplay.isSlime then
                -- 如果不是辅怪，适当调整血条的位置（目前辅怪的大小与普通怪物差不多，暂时无需调整）
                if dbase["atk_bar_pos"] then
                    atkBarNode:setPosition(cc.p(dbase["atk_bar_pos"][1], dbase["atk_bar_pos"][2]));
                elseif not ArenaM.isChildSlime(self.gridData.monster) then
                    hpBarNode:setPosition(cc.p(-70, 105));
                    atkBarNode:setPosition(cc.p(-70, -10));
                end
            end

        -- 观众怪
        elseif self.gridData.monster:isAudience() then
            -- 不显示攻击和血量和背景框
            hpBarNode:setVisible(false);
            atkBarNode:setVisible(false);
            monsterFrame:setOpacity(0);
        elseif FormulaM.invoke("IS_PIRATE_SHIP_MONSTER", classId) == true then
            -- 不显示背景框
            monsterFrame:setOpacity(0);
            -- 创建幽灵船水流动的动画
            -- playEffect(self.monsterNode, 1218);
            self.monsterNode.waterEffectNode = cc.Node:create();
            playEffect(self.monsterNode.waterEffectNode, 1218);

            self.monsterNode:addChild(self.monsterNode.waterEffectNode);
            self.monsterNode.waterEffectNode:setLocalZOrder(self.monsterNode.monsterDisplay:getLocalZOrder() - 1);
        else
            -- 配置了隐藏背景框
            if dbase["hide_monster_frame"] == 1 then
                monsterFrame:setOpacity(0);
            else
                monsterFrame:setOpacity(255);
            end
        end

        -- 活动副本部分楼层需要隐藏底座
        if ActivityDungeonM.needHideFrame() then
        monsterFrame:setOpacity(0);
        self.monsterShadow = ccui.ImageView:create(getLevelItemIconPath("shadow"));
        self.monsterShadow:setPositionY(10);
        self.monsterShadow:setLocalZOrder(-1);
        self.monsterNode:addChild(self.monsterShadow);
        end
    end

    self.attackMonsterClick = onClicked;
end

-- 显示怪物信息
function UIGrid:showMonsterInfo(classId)
    if self.gridData.monster.dbase:query("forbid_longpress") == 1 then
        -- 禁止长按
        return;
    end

    self.isLongPress = true;

    if self.gridData.monster then
        if self.gridData.monster:isBoss() then
            local uiBossInfo = UIBossMonsterInfo.create(classId, self.gridData.monster);
            UIMgr.getCurrentScene():addForm(uiBossInfo);
        elseif self.gridData.type == GRID_TYPE_GHOST_SHIP then
            local uiInfo = UIShipMonsterInfo.create(self.gridData.monster);
            UIMgr.getCurrentScene():addForm(uiInfo);
        else
            local uiInfo = UIMonsterInfo.create(classId, self.gridData.monster);
            UIMgr.getCurrentScene():addForm(uiInfo);
        end
        -- 音效
        AudioM.playFx("ui_open1");
    end
end

-- 显示召唤兽信息
function UIGrid:showSummonInfo()
    self.isLongPress = true;

    if self.gridData.summon then
        if not MonsterCaptainM.isMonsterCaptain(self.gridData.summon) then
            local uiInfo = UISummonInfo.create(self.gridData.summon);
            UIMgr.getCurrentScene():addForm(uiInfo);
        end

        AudioM.playFx("ui_open1");
    end
end

-- 切换怪物动作
function UIGrid:switchMonsterAction(actionType, extra)
    Profiler.funcBegin("switchMonsterAction");

    local pos = self.gridData:getPos();
    if not self.gridData.monster then
        cclog("格子(%d)不是怪物，无法切换怪物动作。", pos);
        return;
    end

    local classId = self.gridData.monster.classId;
    local monsterType = MonsterM.query(classId, "monster_type");
    local dbase = MonsterM.query(classId, "dbase");

    -- 如果是观众怪
    if monsterType == MONSTER_TYPE_AUDIENCE then
        if not self.audienceImg then
            return;
        end

        -- 受创
        if actionType == MONSTER_ACTION_DAMAGED then
            if type(dbase["shake_effect"]) == "number" then
                -- 震动一下
                playShakeEffect(self.audienceImg, 0.03, 4, 4);
            elseif type(dbase["no_shake"]) == "number" then
                -- 不需要后仰
            else
                -- 计算后仰角度
                local angle = 10;

                local skewTo1 = cc.SkewTo:create(0.2, angle, 0);
                local skewTo2 = cc.SkewTo:create(0.2, 0, 0);

                self.audienceImg:runAction(cc.Sequence:create(skewTo1, skewTo2));--
            end
        end

        return;
    end

    if self.monsterNode.monsterDisplay then
        -- 如果是冈布奥怪并且当前的模型是冈布奥
        if (monsterType == MONSTER_TYPE_SLIME or dbase["slime_display"])
            and self.monsterNode.monsterDisplay.isSlime then
            if actionType == MONSTER_ACTION_DAMAGED then
                -- TODO 目前冈布奥怪每轮都会攻击，暂不播放受创
                return;
            end
            local petId;
            local classId = self.gridData.monster.classId;
            -- 史莱姆怪
            if ArenaM.isInArena() then
                -- 竞技场
                petId = ArenaM.querySlime(classId, "pet_id");
            elseif ActivityDungeonM.isInActivityBossLayer() then
                -- 活动副本
                petId = ActivityBossM.queryBoss(classId, "pet_id");
            elseif type(dbase["pet_id"]) == "number" then
                petId = dbase["pet_id"];
            end
            if type(petId) == "number" then
                -- 将怪物动作映射为冈布奥动作
                local actionMap = {
                    [MONSTER_ACTION_IDLE]    = HERO_ACTION_IDLE,       -- 待机
                    [MONSTER_ACTION_DAMAGED] = HERO_ACTION_DAMAGED,    -- 受创
                    [MONSTER_ACTION_ATTACK]  = HERO_ACTION_ATTACK,     -- 攻击
                    [MONSTER_ACTION_CAST]    = HERO_ACTION_CAST_OTHER, -- 施法
                };
                actionType = actionMap[actionType] or HERO_ACTION_IDLE;
            end

            self.monsterNode.monsterDisplay:switchAction(actionType);
        else
            self.monsterNode.monsterDisplay:switchAction(actionType, extra);
        end
    end

    Profiler.funcEnd("switchMonsterAction");
end

-- 恢复待机动画
function UIGrid:playMonsterAction()
    Profiler.funcBegin("playMonsterAction");

    -- 把沙漏变回去
    if self.isPause then
        self:showNormalCd();
        self.isPause = false;
    end

    if self.gridData.monster and self.gridData.monster.isAudience
        and self.gridData.monster:isAudience() then
        -- 观众就不用暂停动作了
        return;
    end

    if self.monsterNode.monsterDisplay then
        self.monsterNode.monsterDisplay:setNeedPause(false);
        self:switchMonsterAction(MONSTER_ACTION_IDLE);
    elseif self.monsterNode.shipDisplay then
        self.monsterNode.shipDisplay:setNeedPause(false);
        self:switchShipAction(SHIP_ACTION_IDLE);
    end

    Profiler.funcEnd("playMonsterAction");
end

-- 是否暂停
function UIGrid:getIsPause()
    return self.isPause;
end

-- 设置暂停
function UIGrid:setIsPause(isPause)
    self.isPause = isPause;
end

-- 是否处于石化状态
function UIGrid:getIsInStone()
    return self.isInStone;
end

-- 设置处于石化状态
function UIGrid:setIsInStone(isInStone)
    self.isInStone = isInStone;
end

-- 检查时间停止暂停动作
function UIGrid:checkTimePause()
    if self.gridData.monster then
        -- 检查怪物模型
        self:pauseMonsterAction();
    elseif self.gridData.type == GRID_TYPE_INSTANCE then
        local instanceId = self.gridData.instanceId;
        if not DungeonTentM.isTentInstance(instanceId) then
            return;
        end

        -- 检查玩家是否使用了时间停止
        local prop = PropM.combine(ME.user, "stop_action", 1);
        if PropM.apply(prop, 1) > 0 then
            self:showForzenCd();
            self:setIsPause(true);
        end
    elseif self.gridData.type == GRID_TYPE_BOSS_STONE then
        if not DungeonBossM.isInBossLayer() then
            return;
        end

        -- 检查玩家是否使用了时间停止
        local prop = PropM.combine(ME.user, "stop_action", 1);
        if PropM.apply(prop, 1) > 0 then
            self:showForzenCd();
            self:setIsPause(true);
        end
    end
end

-- 暂停怪物动作
function UIGrid:pauseMonsterAction(force)
    local pos = self.gridData:getPos();
    if not self.gridData.monster then
        -- cclog("格子(%d)不是怪物，无法暂停怪物动作。", pos);
        return;
    end

    if not self.monsterNode.monsterDisplay and not self.monsterNode.shipDisplay then
        -- cclog("格子(%d)怪物模型尚未创建，无法暂停动作。", pos);
        return;
    end

    if not force and not self.gridData.monster.dbase:query("isPause") then
        -- cclog("格子(%d)非强制且不是暂停状态，无法暂停动作。", pos);
        return;
    end

    -- 显示冻结沙漏
    self:showForzenCd();
    self.isPause = true;

    if self.gridData.monster.isAudience
        and self.gridData.monster:isAudience() then
        -- 观众就不用暂停动作了
        return;
    end

    -- 暂停动作
    if self.monsterNode.monsterDisplay then
        self.monsterNode.monsterDisplay:pauseActions();
        self.monsterNode.monsterDisplay:setNeedPause(true);
    elseif self.monsterNode.shipDisplay then
        self.monsterNode.shipDisplay:pauseActions();
        self.monsterNode.shipDisplay:setNeedPause(true);
    end
end

-- 显示冻结沙漏
function UIGrid:showForzenCd()
    -- 显示冻结沙漏
    local cdNode;
    if self.gridData.monster or self.gridData.summon then
        cdNode = findChildByName(self.barNode, "cd_node");
    else
        cdNode = findChildByName(self.node, "cd_node");
    end
    local cdBg = findChildByName(cdNode, "bg");
    local cdBg2 = findChildByName(cdNode, "bg2");
    local cdLabel = findChildByName(cdNode, "cd");
    local cd = self:getCd();
    cdBg:setVisible(false);
    cdBg2:setVisible(true);
    if not cd or cd < 0 then
        cdLabel:setVisible(false);
    end
    cdNode:setVisible(true);
end

-- 恢复沙漏
function UIGrid:showNormalCd()
    local cd = self:getCd();
    local cdNode;
    if self.gridData.monster or self.gridData.summon then
        cdNode = findChildByName(self.barNode, "cd_node");
    else
        cdNode = findChildByName(self.node, "cd_node");
    end
    local cdBg = findChildByName(cdNode, "bg");
    local cdBg2 = findChildByName(cdNode, "bg2");

    if not cd or cd < 0 then
        cdNode:setVisible(false);
    end
    cdBg2:setVisible(false);
    cdBg:setVisible(true);
end

-- 怪物变身
function UIGrid:monsterTransform(classId)
    local pos = self.gridData:getPos();
    if not self.gridData.monster then
        -- cclog("格子(%d)不是怪物，无法更换模型。", pos);
        return;
    end

    if not self.monsterNode.monsterDisplay and not self.monsterNode.shipDisplay then
        -- cclog("格子(%d)怪物模型尚未创建，无法更换模型。", pos);
        return;
    end

    -- 如果在monster_skill_display.csv里有配置变身特效，那么使用自己的变身效果
    local dbase = MonsterM.query(classId, "dbase");
    local skillId = dbase["transform_skill_id"];

    if type(skillId) == "number" then
        local monsterDisplay = MonsterDisplayM.getMonsterSkillDisplay(classId, skillId);
        if monsterDisplay and monsterDisplay.boss_effect == 1 then
            if UIMgr.getCurrentScene():isOpen("UIBossEffect") then
                UIMgr.getCurrentScene():removeFormByName("UIBossEffect");
            end

            -- 如果有配置了技能发言，那么延迟个0.5秒让玩家看清对话再进特效播放界面
            local delay = 0;
            local talkInfo = AnimationTalkM.getActionSkillTalk(classId, MONSTER_ACTION_ATTACK, skillId);

            if talkInfo and #table.keys(talkInfo) > 0 then
                delay = 0.5;
            end

            -- 锁屏下，免得玩家继续操作
            wait(2);

            performWithDelay(self, function ()
                local actionType = MONSTER_ACTION_ATTACK;

                -- 根据配置，创建新的伴生物的外观
                local accompanyId = dbase["accompany_monster_id"];
                if type(accompanyId) == "number" then
                    actionType = MONSTER_ACTION_ACCOMPANY;
                    self:changeAccompanyDisplay(accompanyId);
                end

                -- 创建新的外观
                self:changeMonsterDisplay(classId, skillId, true);

                -- 打开特效播放界面
                local uiTreasure = UIBossEffect.create(actionType, {["pos"] = pos, ["skillId"] = skillId, ["startFrame"] = {0, 38},});
                UIMgr.getCurrentScene():addForm(uiTreasure);
            end, delay)
        end
    else
        -- 播放烟尘特效
        if MonsterM.query(classId, "monster_type") == MONSTER_TYPE_BOSS then
            playEffect(self, 1270, -300, 420, nil, 2);
        else
            playEffect(self, 1270, 0, 50);
        end

        local changeClassId = self.gridData.monster.dbase:query("changeClassId");

        -- 当前没有被变身才进行模型变换
        if not changeClassId then
            -- 根据配置，创建新的伴生物的外观
            local accompanyId = dbase["accompany_monster_id"];
            if type(accompanyId) == "number" then
                self:changeAccompanyDisplay(accompanyId);
            end

            self:changeMonsterDisplay(classId);
        end
    end

    -- 刷新下血量和攻击
    self:updateHPAndAttack();
end

-- 更换怪物的外观
function UIGrid:changeMonsterDisplay(classId, skillId, isHide)
    -- 移除模型
    self:destroyMonsterDisplay();

    -- 创建新怪物模型
    local monsterDisplay = self:createMonsterDisplay(classId, {["skillId"] = skillId});
    -- 更换模型
    self.monsterNode.monsterDisplay = monsterDisplay;
    self.monsterNode:addChild(monsterDisplay);
    if isHide then self.monsterNode.monsterDisplay:setVisible(false) end
end

-- 更换伴生物的外观
function UIGrid:changeAccompanyDisplay(id)
    -- 移除模型
    self:destroyAccompanyDisplay();

    -- 创建新怪物模型
    local accompanyDisplay = self:createAccompanyDisplay(id);

    -- 更换模型
    self.monsterNode.accompanyDisplay = accompanyDisplay;
    self.monsterNode:addChild(accompanyDisplay);
end

-- 更换怪物模型
function UIGrid:changeMonsterModel()
    Profiler.funcBegin("changeMonsterModel");
    local pos = self.gridData:getPos();
    if not self.gridData.monster then
        -- cclog("格子(%d)不是怪物，无法更换模型。", pos);
        return;
    end

    if not self.monsterNode.monsterDisplay and not self.monsterNode.shipDisplay then
        -- cclog("格子(%d)怪物模型尚未创建，无法更换模型。", pos);
        return;
    end

    local changeClassId = self.gridData.monster.dbase:query("changeClassId");

    if not changeClassId then
        -- cclog("格子(%d)怪物没有指定更换的模型。", pos);
        return;
    end

    -- 格子上怪物隐藏
    self:gotoSelectState(true);

    -- 移除模型
    self:destroyMonsterDisplay();

    -- 移除幽灵船模型
    self.monsterNode:removeChild(self.monsterNode.shipDisplay);
    self.monsterNode.shipDisplay = nil;

    -- 创建新怪物模型
    local display = self:createExtraMonsterDisplay(changeClassId);

    -- 更换模型
    self.monsterNode.monsterDisplay = display;
    self.monsterNode:addChild(display);

    if self:getIsPause() then
        -- 需要暂停动作
        display:setNeedPause(true);
        display:pauseActions();
    else
        display:setNeedPause(false);
    end
    Profiler.funcEnd("changeMonsterModel");
end

-- 还原模型
function UIGrid:showOriMonsterModel()
    Profiler.funcBegin("showOriMonsterModel");
    local pos = self.gridData:getPos();
    if not self.gridData.monster then
        -- cclog("格子(%d)不是怪物，无法还原模型。", pos);
        return;
    end

    if not self.monsterNode.monsterDisplay then
        -- cclog("格子(%d)怪物模型尚未创建，无法还原模型。", pos);
        return;
    end

    local classId = self.gridData.monster:query("class_id");
    local ship = self.gridData.monster;

    if not classId and not ship.classId then
        -- cclog("格子(%d)怪物没有可以还原的模型。", pos);
        return;
    end

    -- 格子上怪物隐藏
    self:gotoSelectState(true);

    -- 移除模型
    self:destroyMonsterDisplay();

    if self.gridData.monster.type == OBJECT_TYPE_GHOST_SHIP then
        -- 创建幽灵船外观
        createShipModel(self.monsterNode, ship.classId);
        self.monsterNode.monsterDisplay = nil;
    else
        -- 创建怪物模型
        local display = self:createMonsterDisplay(classId);
        if not display then
            return;
        end

        -- 更换模型
        self.monsterNode.monsterDisplay = display;
        self.monsterNode:addChild(display);

        self:updateEnergy();
        if self:getIsPause() then
            -- 需要暂停动作
            display:setNeedPause(true);
            display:pauseActions();
        else
            display:setNeedPause(false);
        end
    end
    Profiler.funcEnd("showOriMonsterModel");
end

-- 切换魔藤动作
function UIGrid:switchBineAction(actionType)
    Profiler.funcBegin("switchBineAction");
    if not self.monsterNode.bineDisplay then
        cclog("魔藤模型尚未创建，无法切换动作。");
        return;
    end

    -- 切换状态
    self.monsterNode.bineDisplay:switchAction(actionType);
    Profiler.funcEnd("switchBineAction");
end

-- 切换幽灵船动作
function UIGrid:switchShipAction(actionType)
    Profiler.funcBegin("switchShipAction");
    if not self.monsterNode.shipDisplay then
        cclog("幽灵船模型尚未创建，无法切换动作。");
        return;
    end

    -- 切换状态
    self.monsterNode.shipDisplay:switchAction(actionType);
    Profiler.funcEnd("switchShipAction");
end

-- 切换召唤兽动作
function UIGrid:switchSummonAction(actionType)
    Profiler.funcBegin("switchSummonAction");
    if not self.monsterNode.summonDisplay then
        cclog("召唤兽模型尚未创建，无法切换动作。");
        return;
    end

    -- 切换状态
    self.monsterNode.summonDisplay:switchAction(actionType);
    Profiler.funcEnd("switchSummonAction");
end

-- 切换主教动作
function UIGrid:switchBishopAction(actionType)
    Profiler.funcBegin("switchBishopAction");
    if not self.monsterNode.bishopDisplay then
        cclog("主教模型尚未创建，无法切换动作。");
        return;
    end

    -- 切换状态
    self.monsterNode.bishopDisplay:switchAction(actionType);
    Profiler.funcEnd("switchBishopAction");
end


-- 初始化银行
function UIGrid:initBank()
    self.item:removeAllChildren();

    local function onClicked(sender, eventType)
        if eventType == ccui.TouchEventType.began then
            self:updateBuildingVisit();
        end
    end

    -- 银行图标
    local icon = getLevelItemIconPath("bank");
    self.icon = ccui.ImageView:create(icon);
    self.icon:setTouchEnabled(true);
    self.icon:addTouchEventListener(onClicked);
    self.item:addChild(self.icon);
end

-- 能否出现幽灵船坞
function UIGrid:checkShowGhostDock()
    -- 目前有幽魂水晶保底，直接返回true
    return true;

--    local baseSkillInfo = GhostShipM.goodBaseSkillInfo();
--
--    for goodId, baseSkillId in pairs(baseSkillInfo) do
--        if not GhostShipM.checkSkillLevelMax(goodId) then
--            -- 未到最高级别
--            return true;
--        end
--    end
--
--    if GhostShipM.checkCanLevelUp() then
--       -- 可以升级
--        return true;
--    end
--
--    return false;
end

-- 初始化冈布奥
function UIGrid:initSlime()
    self.item:removeAllChildren();

    self.pickSlimeClck = onClicked;

    -- 冈布奥图标
    local bonus = self.gridData.bonus;
    local classId = bonus[2];
    local fileName = PetM.getDefaultIcon(classId);

    local heroIcon = getHeroIconPath(fileName);

    if PetM.getMyPet(classId) then
        local bonus = FormulaM.invoke("CALC_HERO_TO_MATERIAL", ME.user.pets[classId]);
        self.gridData.bonus = bonus;
        self.gridData.type = GRID_TYPE_ITEM;
        self.gridData.need_shadow = 1;
        self:initItem(self.gridData);
        return;
    end

    self.icon = ccui.ImageView:create(heroIcon);
    self.icon:setPositionY(15);
    self.icon:setTouchEnabled(true);
    self.item:addChild(self.icon);
    self.needAnimation = true;

    self.icon:setVisible(true);
    local function onClicked(sender, eventType)
        if eventType == ccui.TouchEventType.began then
            if self.gridData.state == GRID_STATE_DISABLE then
                -- 已经失效，不处理
                return;
            end

            local function nextStep()
                -- 营救
                local result = DungeonActionM.go("pick_item", self.index);
                if true == result then
                    self.pickSlimeClck = nil;
                    EventMgr.fire(event.PLAYER_MOVE, self.index);
                elseif type(result) == "string" then
                    -- 提示  击杀该层全部怪物后可解救
                    alert(result, false);
                end
            end

            nextStep();
            end
    end
    self.icon:addTouchEventListener(onClicked);

    -- 显示监狱图标
    if self.gridData.need_prison == 1 then
        local prisonIcon = getLevelItemIconPath("prison");
        -- 神龙夹层的奴隶冈布奥
        if UnrealDungeonM.isInUnreal() and classId == 55 then
            -- 监狱图标
            prisonIcon = getLevelItemIconPath("prison2");
            -- 放大一点
            self.icon:setScale(1.1);
            self.icon:setPositionY(10);
        end
        local prisonImg  = ccui.ImageView:create(prisonIcon);
        prisonImg:setName("prison_icon");
        self.item:addChild(prisonImg);
    end
end

-- 初始化冈布奥学徒
function UIGrid:initApprentice()
    self.item:removeAllChildren();

    local function onClicked(sender, eventType)
        if eventType == ccui.TouchEventType.began then
            if self.gridData.state == GRID_STATE_DISABLE then
                -- 已经失效，不处理
                return;
            end
            -- 拾取
            local result = DungeonActionM.go("pick_item", self.index);
            if true == result then
                self.pickApprentice = nil;
                EventMgr.fire(event.PLAYER_MOVE, self.index);
            elseif type(result) == "string" then
                -- 提示  击杀该层全部怪物后可解救
                alert(result,false)
            end
        end
    end

    self.pickApprentice = onClicked;

    -- 冈布奥学徒图标
    local apprenticeIcon = getLevelItemIconPath("apprentice");
    self.icon = ccui.ImageView:create(apprenticeIcon);
    self.icon:setTouchEnabled(true);
    self.icon:setPositionY(5);
    self.icon:addTouchEventListener(onClicked);
    self.item:addChild(self.icon);
    self.needAnimation = true;

    -- 显示监狱图标
    if self.gridData.need_prison == 1 then
        local prisonIcon = getLevelItemIconPath("prison");
        -- 神龙夹层的学徒，另用一个监狱图标
        if UnrealDungeonM.isInUnreal() then
            prisonIcon = getLevelItemIconPath("prison2");
        end
        local prisonImg  = ccui.ImageView:create(prisonIcon);
        prisonImg:setName("prison_icon");
        self.item:addChild(prisonImg);
    end
end

-- 初始化地图
function UIGrid:initMap()
    self.item:removeAllChildren();

    local function onClicked(sender, eventType)
        if  eventType == ccui.TouchEventType.began and
            self.gridData:canHit() then
            -- 拾取
            local result = DungeonActionM.go("pick_item", self.index);
            if true == result then
                self.pickMapClick = nil;
                EventMgr.fire(event.PLAYER_MOVE, self.index);
            elseif type(result) == "string" then
                alert(result, false);
            end
        end
    end

    self.pickMapClick = onClicked;

    -- 地图图标
    local mapIcon = getLevelItemIconPath("map");
    self.icon = ccui.ImageView:create(mapIcon);
    self.icon:setTouchEnabled(true);
    self.icon:addTouchEventListener(onClicked);

    -- 需要显示阴影
    self.hasItem = true;
    self.hasShadow = true;

    if self.offsetByY == true then
        self.icon:setPositionY(75);
    else
        self.icon:setPositionY(25);
    end
    self.itemShadow = ccui.ImageView:create(getLevelItemIconPath("shadow"));
    self.itemShadow:setPositionY(10);
    self.item:addChild(self.itemShadow);

    self.item:addChild(self.icon);
end

-- 初始化宝箱
function UIGrid:initBox()
    self.item:removeAllChildren();

    local monster = self.gridData.monster;

    -- 创建怪物模型
    if monster and not monster:isDead() then
        self:createMonster();
        -- 怪物攻击
        self.attack = self.gridData.attack;
        self.hasMonster = true;
    end

    self:initItem(self.gridData);
    self.item:setVisible(false);
end

-- 初始化幸运喷泉
function UIGrid:initFountain()
    local class = self.gridData.class;
    local buildingInfo = BuildingBonusM.queryBuildingView(class);

    if buildingInfo then
        self:initCommonBuilding();
        return;
    end

    self.item:removeAllChildren();

    local function onClicked(sender, eventType)
        if eventType == ccui.TouchEventType.began then
            if self.gridData.state == GRID_STATE_DISABLE then
                -- 已经失效，不处理
                return;
            end

            -- 更新建筑访问
            self:updateBuildingVisit();

            -- 获取格子位置
            local pos = self.gridData:getPos();

            -- 音效
            AudioM.playFx("ui_open1");

            -- 打开幸运喷泉界面
            if UIMgr.getCurrentScene():isOpen("UIDungeonFountain") then
                UIMgr.getCurrentScene():removeFormByName("UIDungeonFountain");
            end
            local uiDungeonFountain = UIDungeonFountain.create(pos);
            UIMgr.getCurrentScene():addForm(uiDungeonFountain);
        end
    end

    -- 喷泉图标
    local icon = getLevelItemIconPath("fountain");
    self.icon = ccui.ImageView:create(icon);
    self.icon:setTouchEnabled(true);
    self.icon:addTouchEventListener(onClicked);
    self.item:addChild(self.icon);

    fountainEffect(self.icon, 64, 70);
end

-- 初始化矿脉
function UIGrid:initMine()
    self.item:removeAllChildren();

    local function onClicked(sender, eventType)
        if eventType == ccui.TouchEventType.began then
            if self.gridData.state == GRID_STATE_DISABLE then
                -- 已经失效，不处理
                return;
            end

            -- 更新建筑访问
            self:updateBuildingVisit();

            -- 获取格子位置
            local pos = self.gridData:getPos();

            -- 音效
            AudioM.playFx("ui_open1");
        end
    end

    -- 矿脉图标
    local icon = getLevelItemIconPath("mine");
    self.icon = ccui.ImageView:create(icon);
    self.icon:setTouchEnabled(true);
    self.icon:addTouchEventListener(onClicked);
    self.item:addChild(self.icon);
end

-- 创建矿山
function UIGrid:initMineHill()
    local mineId = self.gridData.mine_id;
    local pos = self.gridData:getPos();

    local function onClicked(sender, eventType)
        if eventType == ccui.TouchEventType.began then
            -- 不能采矿
            if not MineHillM.canMining(pos) then
                return;
            end

            -- 更新建筑访问
            self:updateBuildingVisit();

            local mineId = self.gridData.mine_id;
            if MineHillM.query(mineId, "type") == MINE_TYPE_EGG then
                -- 金蛋
                if not self.gridData:canHit() then
                    alert(getLocStr("cannot_reach"));
                    return;
                end
            elseif #MineHillM.findEmptyPos() <= 0 then
                -- 如果没有空位了
                alert(getLocStr("no_pos_for_mine"));
                return;
            end

            -- 采矿
            DungeonActionM.go("mining_diamond", pos);
            EventMgr.fire(event.PLAYER_MOVE, pos);
        end
    end

    -- 建筑图标
    local iconPath = getLevelItemIconPath(MineHillM.query(mineId, "icon"));

    self.icon = ccui.ImageView:create(iconPath);
    self.icon:setTouchEnabled(true);
    self.icon:addTouchEventListener(onClicked);
    self.item:addChild(self.icon);
    self.iconPath = iconPath;

    -- 剩余次数
    self:redrawMineHillLeftTimes();

    if MineHillM.query(mineId, "type") == MINE_TYPE_EGG then
        -- 金蛋先隐藏
        self.icon:setVisible(false);
        self.barNode:setVisible(false);
    end
end

-- 采矿事件
function UIGrid:onMining()
    -- 刷新矿山剩余次数
    self:redrawMineHillLeftTimes();

    -- 播放采矿特效
    playEffect(self, 1237);

    -- 震动一下
    playShakeEffect(self, 0.03, 4, 4);
end

-- 刷新矿山剩余次数
function UIGrid:redrawMineHillLeftTimes()
    local grid = self.gridData;
    if grid.type ~= GRID_TYPE_MINE_HILL or
        grid.state == GRID_STATE_DISABLE then
        return;
    end

    if MineHillM.query(self.gridData.mine_id, "type") == MINE_TYPE_EGG then
        self.barNode:setVisible(false);
    else
        self.barNode:setVisible(true);
    end

    local hpLabel = self.hpLabel;

    -- 显示血量和攻击
    local hp = grid.times or 0;
    hpLabel:setString(tostring(hp));

    local attackNode = self.atkBarNode;
    attackNode:setVisible(false);

    self:setBarStyle(hpLabel);

end

-- 奇怪的盾牌
function UIGrid:initShield()
    -- 查看盾牌
    local function viewShield()
        -- 领取奖励
        if DungeonInstanceM.isInInstance() then
            -- 如果在副本中，走副本奖励流程
            DungeonActionM.go("take_instance_bonus", self.gridData:getPos());
        else
            -- 不在副本中，走通用奖励流程
        end

        EventMgr.fire(event.PLAYER_MOVE, self.gridData:getPos());
    end

    local class = self.gridData.class;
    local buildingInfo = BuildingBonusM.queryBuildingView(class);

    if not buildingInfo then
        error("获取不到建筑查看配置，元素类型：" .. class);
        return;
    end

    local function onClicked(sender, eventType)
        if eventType == ccui.TouchEventType.began then
            if self.gridData.state == GRID_STATE_DISABLE then
                -- 已经失效，不处理
                return;
            end

            -- 更新建筑访问
            self:updateBuildingVisit();

            -- 音效
            AudioM.playFx("ui_open1");

            if DungeonInstanceM.isInInstance() and
                not DungeonM.isAllMonsterDead() then
                -- 如果在副本中，需要击杀所有怪物方可拾取
                self:openBuildingView(nil, -20, buildingInfo["dialog2"], getLocStr("btn_text_leave"));
                return;
            end

            -- 打开建筑查看界面
            self:openBuildingView(viewShield);
        end
    end

    -- 建筑图标
    local iconPath = getLevelItemIconPath(buildingInfo["icon"]);

    self.icon = ccui.ImageView:create(iconPath);
    self.icon:setTouchEnabled(true);
    self.icon:addTouchEventListener(onClicked);
    self.item:addChild(self.icon);
    self.iconPath = iconPath;
end

-- 初始化怪物巢穴副本宝箱
function UIGrid:initCaveBox()
    local function onClicked(sender, eventType)
        if eventType == ccui.TouchEventType.began then
            if self.gridData.state == GRID_STATE_DISABLE then
                -- 已经失效，不处理
                return;
            end

            -- 判断能否拾取
            if not DungeonInstanceM.canPickUpTarget() then
                alert(getLocStr("pick_up_instance_target_fail"));
                return;
            end

            -- 获取格子位置
            local pos = self.gridData:getPos();

            -- 领取奖励
            DungeonActionM.go("take_instance_bonus", pos);
        end
    end

    -- 巢穴宝箱图标
    local boxIcon = getLevelItemIconPath("cave_box");

    self.icon = ccui.ImageView:create(boxIcon);
    self.icon:setTouchEnabled(true);
    self.icon:addTouchEventListener(onClicked);
    self.item:addChild(self.icon);

    boxShiningEffect(self.icon, 70, 100);
end

-- BOSS怪可见
function UIGrid:showBoss()
    -- 显示出来
    self:setVisible(true);
end

-- 资源怪可见
function UIGrid:showIntrudeMonster()
    -- 显示处理
    self:setVisible(true);
    self.monsterNode:setVisible(true);
    self.bineSkillNode:setVisible(false);

    -- 显示右上角的感叹号
    local bgImg = findChildByName(self.bineSkillNode, "bg");
    local iconImg = findChildByName(self.bineSkillNode, "icon");
    bgImg:setTouchEnabled(false);
    bgImg:loadTexture(getCommonIconPath("intrude"));
    iconImg:loadTexture("images/ui/workshop/guide/exclamation_mark.png");

    self.bineSkillNode:setPositionY(self.bineSkillNode:getPositionY() + 10);
    self.bineSkillNode:setScale(0.1)
    performWithDelay(self.bineSkillNode, function ()
        self.bineSkillNode:setVisible(true);

        -- 从小变大
        local scaleUp   = cc.ScaleTo:create(0.5, 0.9);
        self.bineSkillNode:runAction(cc.Sequence:create(scaleUp));
    end, 0.3);

    performWithDelay(self.bineSkillNode, function ()
        -- 播放气泡动画
        playBubbleAnima(self.bineSkillNode, false, 0.9, 0.9, 1, 1.1, 1.5);

        -- 5秒钟后停止
        performWithDelay(self.bineSkillNode, function ()
            self.bineSkillNode:stopAllActions();
            self.bineSkillNode:setVisible(false);
        end, 5);
    end, 0.8);
end

-- 清理怪物节点
function UIGrid:cleanMonsterNode()
    self.monsterNode:removeAllChildren();
    if self.monsterNode.summonDisplay then
        self.monsterNode.summonDisplay = nil;
    end
    if self.monsterNode.monsterDisplay then
        self.monsterNode.monsterDisplay = nil;
    end
end

-- 初始化BOSS怪
function UIGrid:initBoss()
    self:cleanMonsterNode();

    -- 先隐藏普通怪物节点
    self.monsterNode:setVisible(false);

    local clipPanel = findChildByName(self.node, "clip_panel");
    clipPanel:setVisible(true);
    local bossMonsterNode = findChildByName(self.node, "clip_panel/boss_monster");
    bossMonsterNode:setVisible(true);

    -- 需要替换为BOSS怪物节点
    self.monsterNode = bossMonsterNode;

    self:initMonster(true);

    -- 需要先播放动画
    self:setVisible(false);

    local function setZOrder()
        -- 先算出BOSS在第几行
        local y = math.ceil(self.index / GRID_COL_NUM);
        local monster = self.gridData.monster;
        local classId = monster.classId;
        local dbase = MonsterM.query(classId, "dbase");
        local range = dbase["boss_click_range"];

        -- 覆盖覆盖范围是个table，那么算出最大的行数
        if type(range) == "table" then
            y = y + math.ceil((range[1] - 1) / 2);
        end

        -- 设置boss格子的层级为覆盖行最高层级，防止被周围及上面格子覆盖
        self:setLocalZOrder(y * GRID_COL_NUM);
    end

    -- 如果是主BOSS
    if self.gridData:getPos() == DungeonBossM.getBossPos() then
        performWithDelay(self, setZOrder, 0.02);
    end
end

-- 初始化BOSS宝箱
function UIGrid:initBossBox()
    local function onClicked(sender, eventType)
        if eventType == ccui.TouchEventType.began then
            -- 判断能否拾取
            if not DungeonBossM.canPickUpTarget() then
                if self.gridData.is_slime or self.gridData.is_apprentice or self.gridData.need_prison then
                    -- 冈布奥或者学徒，需要提示不能解救
                    alert(getLocStr("not_pickup_slime"),false)
                else
                    alert(getLocStr("pick_up_instance_target_fail"));
                end
                return;
            end

            -- 更新建筑访问
            self:updateBuildingVisit();

            -- 拾取
            local result = DungeonActionM.go("pick_item", self.index);
            if true == result then
                EventMgr.fire(event.PLAYER_MOVE, self.index);
                self.pickBoxClick = nil;
            elseif type(result) == "string" then
                alert(result, false);
            end
        end
    end


    self.pickBoxClick = onClicked;

    -- boss宝箱图标
    -- 需要根据配置的信息有所区分
    local boxIcon = getLevelItemIconPath("boss_box");

    -- 是否需要发光
    local needShining = true;

    if self.gridData.icon ~= nil then
        if self.gridData.icon == "itemself" then
            -- 配置为使用奖励物品自身图标
            local bonus = self.gridData.bonus;
            boxIcon = getItemIconPath(bonus[2]);
        else
            -- 有配置图标，用配置的
            boxIcon = getLevelItemIconPath(self.gridData.icon);
        end
    end

    if self.gridData.is_longzhu == 1 then
        -- 是龙珠
        -- 根据玩家已经有多少个龙珠，来选择图标
        local wishItemCount = DragonWishM.getWishItemCount();

        if wishItemCount > 6 then
            wishItemCount = 6;
        end

        boxIcon = getLevelItemIconPath("dragonball" .. wishItemCount + 1);
    end

    local offsetY    = 0;
    if self.gridData.is_slime then
        -- 是冈布奥，获取冈布奥的头像
        local bonus    = self.gridData.bonus;
        local classId  = bonus[2];
        local fileName = PetM.getDefaultIcon(classId);
        boxIcon    = getHeroIconPath(fileName);
        offsetY    = 15;

        needShining = false;
    end

    if self.gridData.is_apprentice then
        boxIcon    = getLevelItemIconPath(self.gridData.icon);
        offsetY    = 5;
        self.needAnimation = true;
        self:setPositionY(20);
        needShining = false;
    end

    if self.gridData.need_shadow == 1 then
        -- 需要漂浮的要特殊处理
        self.hasShadow = true;
    end

    self.icon = ccui.ImageView:create(boxIcon);

    if offsetY > 0 then
        -- 设置纵向的偏移量
        self.icon:setPositionY(offsetY);
    end

    self.icon:setTouchEnabled(true);
    self.icon:addTouchEventListener(onClicked);
    self.item:addChild(self.icon);

    if needShining then
        shiningEffect(self.icon, 70, 100);
    end

    -- 处理悬浮图标的阴影
    if self.hasShadow then
        self.icon:setPositionY(25);
        self.itemShadow = ccui.ImageView:create(getLevelItemIconPath("shadow"));
        self.itemShadow:setPositionY(10);
        self.item:addChild(self.itemShadow);
    end

    -- 初始时需要监狱图标的遮罩
    local prisonIcon = getLevelItemIconPath("prison");
    local prisonImg  = ccui.ImageView:create(prisonIcon);
    prisonImg:setName("prison_icon");

    self.item:addChild(prisonImg);

    self.hasItem = true;
end

-- 显示建筑查看界面
-- @param viewFunc 查看建筑回调函数
-- @param offsetY  图标纵向偏移量（默认为0）
-- @param dialog   对话内容（默认从配置表中读取）
-- @param viewText 查看按钮文本（默认从配置表中读取）
-- @param title    标题（默认从配置表中读取）
-- @param iconPath 图标路径（默认从配置表中读取）
-- @param disabled 按钮是否置灰
-- @param hideBack 隐藏返回按钮
function UIGrid:openBuildingView(viewFunc, offsetY, dialog, viewText, title, iconPath, disabled, hideBack, para)
    if not DungeonM.checkDungeonAction() then
        return;
    end

    -- 显示建筑查看界面
    UIMgr.getCurrentScene():removeFormByName("UIBuildingView");
    local uiBuildingView = UIBuildingView.create(self.gridData, viewFunc, offsetY, dialog, viewText, title, iconPath, disabled, hideBack, para);
    UIMgr.getCurrentScene():addForm(uiBuildingView);
end

-------------------------------------
-- 显示建筑查看界面（包含额外描述）
-- @param viewFunc 查看建筑回调函数
-- @param offsetY  图标纵向偏移量（默认为0）
-- @param dialog   对话内容（默认从配置表中读取）
-- @param viewText 查看按钮文本（默认从配置表中读取）
-- @param title    标题（默认从配置表中读取）
-- @param iconPath 图标路径（默认从配置表中读取）
-- @param disabled 按钮是否置灰
-- @param hideBack 隐藏返回按钮
function UIGrid:openBuildingView3(viewFunc, offsetY, dialog, viewText, title, iconPath, disabled, hideBack)
    -- 显示建筑查看界面
    UIMgr.getCurrentScene():removeFormByName("UIBuildingView3");
    local uiBuildingView3 = UIBuildingView3.create(self.gridData, viewFunc, offsetY, dialog, viewText, title, iconPath, disabled, hideBack);
    UIMgr.getCurrentScene():addForm(uiBuildingView3);
end

----------------------------------
-- 显示建筑查看界面（含两个按钮）
-- @param okFunc      确定按钮处理函数
-- @param cancelFunc  取消按钮处理函数
-- @param okText      确定按钮文本
-- @param cancelText  取消按钮文本
-- @param dialog      内容文本（默认从配置表中取）
-- @param iconPath    图标路径 (默认从配置表中取)
-- @param hideBack    隐藏返回按钮
-- @param title       标题（默认从配置表中读取）
function UIGrid:openBuildingView2(okFunc, cancelFunc, okText, cancelText, dialog, iconPath, hideBack, title)
    -- 显示建筑查看界面
    UIMgr.getCurrentScene():removeFormByName("UIBuildingView2");
    local uiBuildingView = UIBuildingView2.create(self.gridData, okFunc, cancelFunc, okText, cancelText, dialog, iconPath, hideBack, title);
    UIMgr.getCurrentScene():addForm(uiBuildingView);
end

----------------------------------
-- @param okFunc1  第一个按钮的处理函数
-- @param okFunc2  第二个按钮的处理函数
-- @param okFunc3  第三个按钮的处理函数
-- @param okFunc4  第四个按钮的处理函数
-- @param okText1  第一个按钮的处理函数
-- @param okText2  第二个按钮的处理函数
-- @param okText3  第三个按钮的处理函数
-- @param okText4  第四个按钮的处理函数
-- @param dialog   建筑描述（默认从配置表中取）
-- @param title    建筑标题（默认从配置表中取）
-- @param iconPath 建筑图标（默认从配置表中取）
-- 显示建筑查看界面（含四个按钮）
function UIGrid:openBuildingView4(okFunc1, okFunc2, okFunc3, okFunc4, okText1, okText2, okText3, okText4, dialog, title, iconPath)
    UIMgr.getCurrentScene():removeFormByName("UIBuildingView4");
    local uiBuildingView = UIBuildingView4.create(self.gridData, okFunc1, okFunc2, okFunc3, okFunc4, okText1, okText2, okText3, okText4, dialog, title, iconPath);
    UIMgr.getCurrentScene():addForm(uiBuildingView);
end

----------------------------------
-- @param title        建筑标题（默认从配置表中取）
-- @param dialog       建筑描述（默认从配置表中取）
-- @param iconPath     建筑图标（默认从配置表中取）
-- @param okFuncList   确定按钮回调列表
-- @param okTextList   确定按钮文本列表
-- @param drawIconFunc 画图片的委托函数
-- @param offsetList   间隔距离数组(默认不传)
-- 显示建筑查看界面（按钮纵向按顺序排列，数量，位置自适应）
function UIGrid:openBuildingView5(okFuncList, okTextList, title, dialog, iconPath, drawIconFunc, offsetList, para)
    UIMgr.getCurrentScene():removeFormByName("UIBuildingView5");
    local uiBuildingView = UIBuildingView5.create(self.gridData, okFuncList, okTextList, title, dialog, iconPath, drawIconFunc, offsetList, para);
    UIMgr.getCurrentScene():addForm(uiBuildingView);
end

-----------------------------------
-- 显示建筑提交界面
-- @param okFunc    提交处理函数
-- @param openType  打开包裹的方式：用于指定哪些物品可用于提交
-- @param bonus     传入可用于提交的物品列表，没包含在里面的提交按钮会被置灰，不传该参数默认不置灰
function UIGrid:openBuildingSubmit(okFunc, openType, bonus, extra)
    UIMgr.getCurrentScene():removeFormByName("UIBuildingSubmit");
    local uiForm = UIBuildingSubmit.create(self.gridData, okFunc, openType, bonus, extra);
    UIMgr.getCurrentScene():addForm(uiForm);
end

-- 沉船
function UIGrid:initShipWreck()
    self:initCommonBuilding();
end

-- 水手的残骸
function UIGrid:initSailorWreckage()
    local class = self.gridData.class;
    local buildingInfo = BuildingBonusM.queryBuildingView(class);

    if not buildingInfo then
        error("获取不到水手的残骸建筑查看配置，元素类型：" .. class);
        return;
    end

    local function onClicked(sender, eventType)
        if eventType == ccui.TouchEventType.began then
            if self.gridData.state == GRID_STATE_DISABLE then
                -- 已经失效，不处理
                return;
            end

            -- 更新建筑访问
            self:updateBuildingVisit();

            -- 音效
            AudioM.playFx("button_spell");

            -- 水手残骸专用界面
            UIMgr.getCurrentScene():removeFormByName("UIDungeonSailor");
            local uiDungeonSailor = UIDungeonSailor.create(self.gridData:getPos());
            UIMgr.getCurrentScene():addForm(uiDungeonSailor);
        end
    end

    local iconPath = getLevelItemIconPath(buildingInfo["icon"]);
    self.icon = ccui.ImageView:create(iconPath);
    self.icon:setTouchEnabled(true);
    self.icon:addTouchEventListener(onClicked);
    self.item:addChild(self.icon);
end

-- 初始化通用建筑
function UIGrid:initCommonBuilding(callFunc)
    local pos = self.gridData:getPos();
    if not callFunc then
        local function defaultFunc()
            if DungeonInstanceM.isInInstance() and DungeonInstanceM.getTargetGridPos() == pos then
                -- 如果是副本的目标格子，走副本奖励流程
                DungeonActionM.go("take_instance_bonus", self.gridData:getPos());
            else
                DungeonActionM.go("do_building_bonus", pos);
            end
            EventMgr.fire(event.PLAYER_MOVE, pos);

            local buildingInfo = BuildingBonusM.queryBuildingView(self.gridData.class);
            -- 如果配置了奖励提示音效
            if buildingInfo["bonus_audio"] and buildingInfo["bonus_audio"] ~= "" then
                local audio = buildingInfo["bonus_audio"];
                AudioM.playFx(audio);
            end
        end
        callFunc = defaultFunc;
    end

    local class = self.gridData.class;
    local buildingInfo = BuildingBonusM.queryBuildingView(class);

    if not buildingInfo then
        error("获取不到建筑查看配置，元素类型：" .. class);
        return;
    end

    local function onClicked(sender, eventType)
        if eventType == ccui.TouchEventType.began then
            if self.gridData.state == GRID_STATE_DISABLE then
                -- 已经失效，不处理
                return;
            end

            -- 如果配置了监狱图标，需要击杀所有怪物后方能拾取
            if self.gridData.need_prison == 1  and
              not DungeonM.isAllMonsterDead() then
                alert(getLocStr("pick_up_instance_target_fail"));
                return;
            end

            -- 更新建筑访问
            self:updateBuildingVisit();

            -- 音效
            AudioM.playFx("button_spell");

            -- 怪物全清
            local monsterCleard = DungeonM.isAllMonsterDead();

            if DungeonInstanceM.isInInstance() and
               not monsterCleard then
                -- 如果在副本中，需要击杀所有怪物方可拾取
                self:openBuildingView(nil, -20, buildingInfo["dialog2"], getLocStr("btn_text_leave"));
                return;
            end

            -- boss层中
            local dungeonId = DungeonM.getDungeonId();
            local layer     = DungeonM.currentLayer();
            if DungeonBossM.isBossLayer(dungeonId, layer, ME.user) and not monsterCleard then
                alert(getLocStr("pick_up_instance_target_fail"));
                return;
            end

            -- 打开建筑查看界面
            self:openBuildingView(callFunc);
        end
    end

    self.item:removeAllChildren();

    -- 建筑图标
    local iconPath = getLevelItemIconPath(buildingInfo["icon"]);
    self.icon = ccui.ImageView:create(iconPath);
    self.icon:setTouchEnabled(true);
    self.icon:addTouchEventListener(onClicked);
    self.item:addChild(self.icon);
    self.iconPath = iconPath;

    -- 是否闪光
    if buildingInfo["shinning"] == 1 then
        self.shiningAjustPos = cc.p(70, 100);
        shiningEffect(self.icon, self.shiningAjustPos.x, self.shiningAjustPos.y);
    end

    if self.gridData.need_prison == 1  then
        -- 初始时需要监狱图标的遮罩
        local prisonIcon = getLevelItemIconPath("prison");
        local prisonImg  = ccui.ImageView:create(prisonIcon);
        prisonImg:setName("prison_icon");

        self.item:addChild(prisonImg);
    end
end

-- 村民
function UIGrid:initVillager()
    -- 发起谈话
    local function callFunc()
        local pos = self.gridData:getPos();
        local classId = self.gridData.villager_id;

        if VillagerM.canTakeBonus(pos) then
            local bonusInfo = DungeonActionM.go("villager_bonus", pos);
            EventMgr.fire(event.PLAYER_MOVE, pos);

            local tip = VillagerM.getBonusDesc(bonusInfo);
            if type(tip) == "string" and #tip > 0 then
                -- alert(tip);
                local list = splitString(tip, "\n");
                splitAlert(list);
            end
        elseif self.gridData.took  then
            -- 领取过奖励了？尝试武装
            if DungeonActionM.go("arm_villager", pos) then
                -- 武装成功了，播放奖励提示
                local tip = FormulaM.invoke("GET_VILLAGER_ARM_TIP", classId);
                if type(tip) == "string" and #tip > 0 then
                    local list = splitString(tip, "\n");
                    splitAlert(list);
                end
            end
        end
    end

    local classId = self.gridData.villager_id;
    -- 建筑图标
    local icon = VillagerM.query(classId, "icon");
    local iconPath = getLevelItemIconPath(icon);

    local function onClicked(sender, eventType)
        if eventType == ccui.TouchEventType.began then
            if self.gridData.state == GRID_STATE_DISABLE then
                -- 已经失效，不处理
                return;
            end

            -- 更新建筑访问
            self:updateBuildingVisit();

            -- 打开建筑查看界面
            local dialog;
            local btnText;
            local name = VillagerM.query(classId, "name");
            local pos = self.gridData:getPos();
            if VillagerM.canTakeBonus(pos) then
                dialog = VillagerM.query(classId, "talk1");
                btnText = VillagerM.query(classId, "button_text_1");
            elseif not self.gridData.took then
                -- 还未领取过奖励
                dialog = VillagerM.query(classId, "talk0");
                btnText = VillagerM.query(classId, "button_text_0");
            elseif VillagerM.canArm(pos) then
                -- 能够武装
                dialog = FormulaM.invoke("GET_VILLAGER_ARM_DESC", classId);
                btnText = getLocStr("btn_text_go_to_fight");
            else
                return;
            end

            -- 音效
            AudioM.playFx("slime_swordman_app");

            self:openBuildingView(callFunc, 0, dialog, btnText, name, iconPath);
        end
    end

    self.icon = ccui.ImageView:create(iconPath);
    self.icon:setTouchEnabled(true);
    self.icon:addTouchEventListener(onClicked);
    self.item:addChild(self.icon);
    self.iconPath = iconPath;
    self.needAnimation = true;
end

-- 海贼的宝藏
function UIGrid:initTreasure()
    -- 发起谈话
    local function callFunc()
        local pos = self.gridData:getPos();

        if TreasureM.canTakeBonus(pos) then
            DungeonActionM.go("treasure_bonus", pos);
        end
    end

    local class = self.gridData.class;
    local buildingInfo = BuildingBonusM.queryBuildingView(class);

    if not buildingInfo then
        error("获取不到海贼的宝藏建筑查看配置，元素类型：" .. class);
        return;
    end

    local function onClicked(sender, eventType)
        if eventType == ccui.TouchEventType.began then
            if self.gridData.state == GRID_STATE_DISABLE then
                -- 已经失效，不处理
                return;
            end

            -- 更新建筑访问
            self:updateBuildingVisit();

            -- 音效
            AudioM.playFx("ui_open1");

            if not DungeonM.isAllMonsterDead() then
                -- 如果在副本中，需要击杀所有怪物方可拾取
                self:openBuildingView(nil, -20, buildingInfo["dialog2"], getLocStr("btn_text_leave"));
                return;
            end

            -- 打开建筑查看界面
            self:openBuildingView(callFunc, 0, buildingInfo["dialog"], buildingInfo["view_text"]);

        end
    end

    --self:initCommonBuilding(onClicked);

    local iconPath = getLevelItemIconPath(buildingInfo["icon"]);
    self.icon = ccui.ImageView:create(iconPath);
    self.icon:setTouchEnabled(true);
    self.icon:addTouchEventListener(onClicked);
    self.item:addChild(self.icon);
    self.iconPath = iconPath;
end

-- 垃圾堆
function UIGrid:initRubbish()
    self.item:removeAllChildren();

    local dbase = DungeonM.queryElement(self.gridData.element, "dbase");
    if type(dbase) == "table" and type(dbase["with_property_hide"]) == "number" then
        -- 配置了有道具就不出现
        if ItemM.getAmount(ME.user, dbase["with_property_hide"]) > 0 then
            -- 包裹中有需求的道具
            return;
        end
    end

    -- 翻找垃圾堆
    local function callFunc()
        local pos = self.gridData:getPos();
        DungeonActionM.go("rubbish", pos);
        EventMgr.fire(event.PLAYER_MOVE, pos);
    end

    self:initCommonBuilding(callFunc);
end

-- 雕像
function UIGrid:initStatue()
    local function callFunc()
        local pos = self.gridData:getPos();
        DungeonActionM.go("statue", pos);

        EventMgr.fire(event.PLAYER_MOVE, pos);
    end

    self:initCommonBuilding(callFunc);
end

-- 杰克的尸体
function UIGrid:initJackCorpse()
    self:initCommonBuilding();
end

-- 皇后的尸体
function UIGrid:initQueenCorpse()
    self:initCommonBuilding();
end

-- 女人的残骸
function UIGrid:initWomanWrecage()
    self:initCommonBuilding();
end

-- 佐罗的尸体
function UIGrid:initZoro()
    self:initCommonBuilding();
end

-- 朝圣者残骸
function UIGrid:initPilgrim()
    self:initCommonBuilding();
end

-- 神龛
function UIGrid:initShrine()
    self:initCommonBuilding();
end

-- 重绘魔藤技能气泡
function UIGrid:redrawBineSkill()
    redrawBineSkill(self);
end

-- 重绘魔藤的攻击和生命
function UIGrid:redrawBineAttackAndHp()
    redrawBineAttackAndHp(self);
end

-- 重绘主教技能气泡
function UIGrid:redrawBishopSkill()
    if type(redrawBishopSkill) == "function" then
        redrawBishopSkill(self);
    end
end

-- 重绘主教攻击和生命
function UIGrid:redrawBishopAttackAndHp()
    if type(redrawBishopAttackAndHp) == "function" then
        redrawBishopAttackAndHp(self);
    end
end

-- 设置血条字体样式
function UIGrid:setBarStyle(textNode)
    TextStyleM.setTextStyle(textNode, TextStyleM.TEXT_SIZE_MINI, TextStyleM.TEXT_COLOR_WHITE, true, -2);
    TextStyleM.setOutlineStyle(textNode, TextStyleM.TEXT_OUTLINE_SIZE, TextStyleM.TEXT_OUTLINE_COLOR_DARK);
    textNode:setScaleX(1.2);
end

-- 怪物发言
function UIGrid:doMonsterTalk()
    local pos = self.gridData:getPos();
    if not self.gridData:isMonster() and
        self.gridData.type ~= GRID_TYPE_BELIEVER then
        -- 不是怪物和信徒，不能发言
        trace("UIGrid", "格子(%d)不是怪物或信徒，不能发言。", pos);
        return;
    end

    -- 淡出时间
    local fadeTime = 2;

    if pos % DUNGEON_WIDTH == 0 then
        -- 如果格子在最右侧一列，则需要修正一下发言节点的位置
        if self.talkNode:getPositionX() == self.talkNode.initPosX then
            self.talkNode:setPositionX(-self.talkNode.initPosX);

            -- 水平翻转气泡
            local bg = findChildByName(self.talkNode, "bg");
            bg:setFlippedX(true);

            local initZOrder = self:getLocalZOrder();

            -- 临时调高ZOrder，防止被左侧的格子盖住
            self:setLocalZOrder(initZOrder + 4);

            local function restoreZOrder()
                self:setLocalZOrder(initZOrder);
            end

            -- 定时还原ZOrder
            performWithDelay(self, restoreZOrder, fadeTime)
        end
    else
        -- 不在最后一列时，如果发现气泡位置不在初始位置，则修正回来
        if self.talkNode:getPositionX() ~= self.talkNode.initPosX then
            self.talkNode:setPositionX(-self.talkNode.initPosX);

            -- 水平翻转气泡
            local bg = findChildByName(self.talkNode, "bg");
            bg:setFlippedX(true);
        end
    end

    -- 获取说话内容
    local talkId = MonsterM.getTalkId();
    if self.gridData.type == GRID_TYPE_BELIEVER then
        -- 信徒的发言
        talkId = TalkTemplateM.getTalkId(GRID_TYPE_BELIEVER, 1);
    end

    self.talkNode:setVisible(true);
    self.talkNode:setOpacity(255);

    local content = TalkTemplateM.query(talkId, "content");
    local contentLabel = findChildByName(self.talkNode, "content");
    local kerning = 0;
    if getLang() ~= "zh" and getLang() ~= "tw" then
        kerning = -2;
    end
    TextStyleM.setTextStyle(contentLabel, TextStyleM.TEXT_SIZE_TINY, TextStyleM.TEXT_COLOR_BROWN_YELLOW, true, kerning);
    contentLabel:setString(content);

    self.talkNode:stopAllActions();
    self.talkNode:runAction(cc.Sequence:create(cc.DelayTime:create(0.5), cc.FadeOut:create(fadeTime)));

    -- 音效
    local audio = TalkTemplateM.query(talkId, "audio_id");
    if type(audio) == "string" and #audio > 0 then
        AudioM.playFx(audio);
    end
end

-- 骸骨堆
function UIGrid:initSkeleton()
    self:initCommonBuilding();
end

-- 头骨祭坛
function UIGrid:initHeadAltar()
    self:initCommonBuilding();
end

-- 提交元素圣坛水晶的回调
function UIGrid:onSubmitElem(elem, isFinal)
    onSubmitElem(self, elem, isFinal);
end

-- 元素圣坛奖励的回调
function UIGrid:onElemAltarBonus(bonus, disappear)
    onElemAltarBonus(self, bonus, disappear);
end

-- 提交元素圣坛水晶失败回调
function UIGrid:redrawFailSub(dir, elem)
    redrawFailSub(self, dir, elem);
end
-- 弹药箱
function UIGrid:initCaisson()
    self:initCommonBuilding();
end

-- 走私者残骸
function UIGrid:initSkummer()
    self:initCommonBuilding();
end

-- 漂流瓶
function UIGrid:initDriftBottle()
    local function callFunc()
        local pos = self.gridData:getPos();
        DungeonActionM.go("drift_bottle", pos);
        EventMgr.fire(event.PLAYER_MOVE, pos);
    end

    local class = self.gridData.class;
    local buildingInfo = BuildingBonusM.queryBuildingView(class);

    if not buildingInfo then
        error("获取不到建筑查看配置，元素类型：" .. class);
        return;
    end

    local function onClicked(sender, eventType)
        if eventType == ccui.TouchEventType.began then
            if self.gridData.state == GRID_STATE_DISABLE then
                -- 已经失效，不处理
                return;
            end

            -- boss层中
            local dungeonId = DungeonM.getDungeonId();
            local layer     = DungeonM.currentLayer();
            if DungeonBossM.isBossLayer(dungeonId, layer) and not DungeonM.isAllMonsterDead() then
                alert(getLocStr("pick_up_instance_target_fail"));
                return;
            end

            -- 音效
            AudioM.playFx("button_tiny");

            -- 打开建筑查看界面
            self:openBuildingView(callFunc, 20);
        end
    end

    -- 建筑图标
    local iconPath = getLevelItemIconPath(buildingInfo["icon"]);

    self.icon = ccui.ImageView:create(iconPath);
    self.icon:setTouchEnabled(true);
    self.icon:addTouchEventListener(onClicked);
    self.item:addChild(self.icon);
    self.iconPath = iconPath;

    -- 是否闪光
    if buildingInfo["shinning"] == 1 then
        self.shiningAjustPos = cc.p(70, 100);
        shiningEffect(self.icon, self.shiningAjustPos.x, self.shiningAjustPos.y);
    end
end

-- 设置debuff 和buff的可见性，调整位置
function UIGrid:showBuff()
    local buffNode = findChildByName(self.barNode, "buff_node");
    local debuffNode = findChildByName(self.barNode, "debuff_node");
    buffNode:setVisible(false);
    debuffNode:setVisible(false);
    local buffImg = findChildByName(self.barNode, "buff_node/buff_bar");
    local debuffImg = findChildByName(self.barNode, "debuff_node/debuff_bar");

    buffImg:loadTexture(getLevelItemIconPath("buff_bar"));
    debuffImg:loadTexture(getLevelItemIconPath("debuff_bar"));

    if self.gridData.monster and self.gridData.monster:isBoss() and self.gridData.monster:query("dbase")["hide_boss_debuff"] ~= 1 then
        local classId = self.gridData.monster.classId;
        local dbase = MonsterM.query(classId, "dbase");

        -- 如果配置了boss背景框
        if dbase["boss_frame_icon"] then
            local gridWidth  = 128;
            local gridHeight = 130;
            local offsetX = 20;  -- 往右偏移一点，避免血条被盖住

            -- 把偏移数据记录在节点上面
            buffNode.offsetX = buffNode.offsetX or buffNode:getPositionX() - gridWidth + offsetX;
            buffNode.offsetY = buffNode.offsetY or buffNode:getPositionY() + gridHeight - 60;
            buffNode:setPosition(buffNode.offsetX, buffNode.offsetY);

            debuffNode.offsetX = debuffNode.offsetX or debuffNode:getPositionX() - gridWidth + offsetX;
            debuffNode.offsetY = debuffNode.offsetY or debuffNode:getPositionY();
            debuffNode:setPosition(debuffNode.offsetX, debuffNode.offsetY);
        end

        local deDebuff = PropM.getDebuff(self.gridData.monster);
        if #deDebuff > 0 then
           -- buffNode:setVisible(true);
           debuffNode:setVisible(true);
        end
    end
end

-- 实时更新debuff的图标
function UIGrid:updateDebuff()

    local debuffNode = findChildByName(self.barNode, "debuff_node");
    if self.gridData.monster and self.gridData.monster:isBoss() and self.gridData.monster:query("dbase")["hide_boss_debuff"] ~= 1 then
        local deDebuff = PropM.getDebuff(self.gridData.monster);
        if #deDebuff > 0 then
            debuffNode:setVisible(true);
        else
            debuffNode:setVisible(false);
            -- cclog("debuffVisible false");
        end

    end
    performWithDelay(self, function() self:updateDebuff(); end, 1);
end

-- 元素矿脉
function UIGrid:initElemMine()
    self:initCommonBuilding();
end

-- 胜利者石柱
function UIGrid:initWinnerStone()
    local function viewStone()
        -- 石柱奖励
        DungeonActionM.go("do_building_bonus", self.gridData:getPos());
        AudioM.playFx("hurray");
    end

    local class = self.gridData.class;
    local buildingInfo = BuildingBonusM.queryBuildingView(class);

    if not buildingInfo then
        error("获取不到建筑查看配置，元素类型：" .. class);
        return;
    end

    local function onClicked(sender, eventType)
        if eventType == ccui.TouchEventType.began then
            if self.gridData.state == GRID_STATE_DISABLE then
                -- 已经失效，不处理
                return;
            end

            -- 更新建筑访问
            AudioM.playFx("ui_open1");
            self:updateBuildingVisit();

            if not DungeonM.isAllMonsterDead() then
                -- 需要击杀所有怪物方可拾取
                self:openBuildingView(nil, -20, buildingInfo["dialog2"], getLocStr("btn_text_leave"));
                return;
            end

            -- 打开建筑查看界面
            self:openBuildingView(viewStone);
        end
    end

    -- 建筑图标
    local iconPath = getLevelItemIconPath(buildingInfo["icon"]);

    self.icon = ccui.ImageView:create(iconPath);
    self.icon:setTouchEnabled(true);
    self.icon:addTouchEventListener(onClicked);
    self.item:addChild(self.icon);
    self.iconPath = iconPath;

    -- 整体往上偏移一点
    self.item:setPositionY(20);
end

-- 魅魔小屋
function UIGrid:initcabin()
    local function doCabin()
        DungeonActionM.go("do_building_bonus", self.gridData:getPos());
    end

    local function viewCabin()
        playCabinEffect(self.node);
        -- 小屋奖励
        performWithDelay(self, doCabin, 3.5);
    end

    local class = self.gridData.class;
    local buildingInfo = BuildingBonusM.queryBuildingView(class);

    if not buildingInfo then
        error("获取不到建筑查看配置，元素类型：" .. class);
        return;
    end

    local function onClicked(sender, eventType)
        if eventType == ccui.TouchEventType.began then
            if self.gridData.state == GRID_STATE_DISABLE then
                -- 已经失效，不处理
                return;
            end

            -- 更新建筑访问
            AudioM.playFx("ui_open1");
            self:updateBuildingVisit();

            if not DungeonM.isAllMonsterDead() then
                -- 需要击杀所有怪物方可拾取
                self:openBuildingView(nil, -20, buildingInfo["dialog2"], getLocStr("btn_text_leave"));
                return;
            end

            -- 打开建筑查看界面
            self:openBuildingView(viewCabin);
        end
    end

    -- 建筑图标
    local iconPath = getLevelItemIconPath(buildingInfo["icon"]);

    self.icon = ccui.ImageView:create(iconPath);
    self.icon:setTouchEnabled(true);
    self.icon:addTouchEventListener(onClicked);
    self.item:addChild(self.icon);
    self.iconPath = iconPath;

    -- 整体往上偏移一点
    self.item:setPositionY(20);
end

-- 关注石柱关闭的事件
function UIGrid:onCloseStone()
    if type(onCloseStone) == "function" then
        onCloseStone(self);
    end

    if self.gridData.type == GRID_TYPE_INTERFERE_DEVICE then
        -- 电流干扰装置
        self:removeChild(self.interfereEffectNode);
        removeInterfereDeviceEffect();

        -- 移除警戒
        AlertStateM.clearState(AlertStateM.ALERT_STATE_INTERFERE_DEVICE);
    end
end

-- 关注学习石柱技能的事件
function UIGrid:onStudyStoneSkill()
    onStudyStoneSkill(self);
end

-- 木牌
function UIGrid:initBoard()
    local class = self.gridData.class;
    local buildingInfo = BuildingBonusM.queryBuildingView(class);
    local offset = {0, 0};

    if not buildingInfo then
        error("获取不到建筑查看配置，元素类型：" .. class);
        return;
    end

    if ActivityDungeonM.isInActivityBossLayer()
        and NormalAudienceM.needFixPosition() then
        offset = self.gridData.offset or {0, 0};
    end

    local function onClicked(sender, eventType)
        if eventType == ccui.TouchEventType.began then
            -- 更新建筑访问
            self:updateBuildingVisit();

            -- 音效
            AudioM.playFx("ui_open1");

            local dbase = DungeonM.query(class, "dbase") or {};
            if dbase["extra_desc"] then
                -- 带额外描述
                self:openBuildingView3();
                return;
            end

            -- 获取当前所在的休息室id
            local roomId = RestRoomM.getCurRestRoomId();
            if roomId then
                -- 获取战队名称
                local name = RestRoomM.query(roomId, "team_name");
                local dialog = string.format(buildingInfo["dialog"], name);
                self:openBuildingView(nil, 0, dialog);
                return;
            end

            -- 活动副本BOSS层
            if ActivityDungeonM.isInActivityBossLayer() and
                self.gridData.dialog_text and self.gridData.btn_text then
                local dialog = getLocStr(self.gridData.dialog_text);
                local btnText = getLocStr(self.gridData.btn_text);
                self:openBuildingView(nil, 0, dialog, btnText, nil, nil, nil, nil, {["auto_align"] = true});
                return;
            end

            -- 神龙召唤夹层迷宫
            local unrealId = UnrealDungeonM.getUnrealId();
            if unrealId then
                local dialog = UnrealDungeonM.query(unrealId, "board");
                self:openBuildingView(nil, 0, dialog);
                return;
            end

            -- 其他木牌，直接打开建筑查看界面
            self:openBuildingView(nil, 0, buildingInfo["dialog"]);
        end
    end

    -- 建筑图标
    local iconPath = getLevelItemIconPath(buildingInfo["icon"]);

    self.icon = ccui.ImageView:create(iconPath);
    self.icon:setTouchEnabled(true);
    self.icon:addTouchEventListener(onClicked);
    self.icon:setPosition(cc.p(offset[1], offset[2]));
    self.item:addChild(self.icon);
    self.iconPath = iconPath;
end

-- 神龙夹层特殊物件
function UIGrid:initUnrealSpecial()
    local class = self.gridData.class;
    local buildingInfo = BuildingBonusM.queryBuildingView(class);

    if not buildingInfo then
        error("获取不到建筑查看配置，元素类型：" .. class);
        return;
    end

    local pos = self.gridData:getPos()
    local function doBonusFun()
        local disappear = false;
        if class == 181 or class == 182 then
            -- 植物采集后消失
            disappear = true;

            local function delay()
                if ItemM.getAmount(ME.user, 7157) > 0 then
                    -- 使用剪刀提示
                    alert(getLocStr("use_scissors_tips"));
                else
                    alert(getLocStr("scissors_broken_tips"));
                end
            end
            performWithDelay(self, delay, 1);
        end

        if class == 180 then
            -- 树桩需要替换图片
            local iconPath = getLevelItemIconPath("stub");
            self.icon:loadTexture(iconPath);
        end

        -- 神龙彩蛋领奖
        DungeonActionM.go("take_dragon_wish_building_bonus", pos, class);

        -- 如果配置了奖励提示
        if buildingInfo["bonus_tip"] then
            alert(buildingInfo["bonus_tip"]);
        end

        -- 移除闪光
        self.icon:removeAllChildren();

        -- 关闭保险箱/猴子页面
        UIMgr.getCurrentScene():removeFormByName("UIStrongBox");
        UIMgr.getCurrentScene():removeFormByName("UIPeterMonkey");

        -- 播放奖励效果
        local bonus = FormulaM.invoke("CALC_DRAGON_WISH_BUILDING_BONUS", class);
        self:playBonusListEffect(bonus, disappear);
    end

    local function onClicked(sender, eventType)
        if eventType == ccui.TouchEventType.began then
            if self.gridData.state == GRID_STATE_DISABLE then
                -- 已经领取过了
                -- 猴子需要显示别的内容
                -- 其他的点击不予理会
                if class == 184 then
                    -- 打开建筑查看界面
                    self:openBuildingView(nil, 0, buildingInfo["dialog"]);
                end
                return;
            end

            -- 更新建筑访问
            self:updateBuildingVisit();

            -- 音效
            AudioM.playFx("ui_open1");

            if class == 183 then
                -- 保险箱
                UIMgr.getCurrentScene():removeFormByName("UIStrongBox");
                local uiBuildingView = UIStrongBox.create(self.gridData, doBonusFun, 0, buildingInfo["dialog"]);
                UIMgr.getCurrentScene():addForm(uiBuildingView);
            elseif class == 184 then
                -- peter(猴子)
                UIMgr.getCurrentScene():removeFormByName("UIPeterMonkey");
                local uiBuildingView = UIPeterMonkey.create(self.gridData, doBonusFun, 0, getLocStr("dragon_wish_monkey"));
                UIMgr.getCurrentScene():addForm(uiBuildingView);
            else
                if class == 181 or class == 182 then
                    -- 植物
                    if ItemM.getAmount(ME.user, 7157) <= 0 then
                        -- 没有园丁剪刀
                        return;
                    end
                end

                -- 打开建筑查看界面
                self:openBuildingView(doBonusFun, 0, buildingInfo["dialog"]);
            end
        end
    end

    -- 建筑图标
    local iconPath = getLevelItemIconPath(buildingInfo["icon"]);

    self.icon = ccui.ImageView:create(iconPath);
    self.icon:setTouchEnabled(true);
    self.icon:addTouchEventListener(onClicked);
    self.item:addChild(self.icon);
    self.iconPath = iconPath;

    -- 如果配置了无法点击，那么去掉点击响应
    local element = self.gridData.element;
    local elementDbase = DungeonM.queryElement(element, "dbase") or {};
    if elementDbase["cannot_click"] then
        self.icon:setTouchEnabled(false);
    end

    -- 需要向上偏移的物件
    local dbase = DungeonM.query(class, "dbase");
    if type(dbase["position_y"]) == 'number' then
        self.item:setPositionY(dbase["position_y"]);
    end

    -- 是否闪光
    if buildingInfo["shinning"] == 1 then
        self.shiningAjustPos = cc.p(70, 100);
        shiningEffect(self.icon, self.shiningAjustPos.x, self.shiningAjustPos.y);
    end
end

-- 神龙夹层普通物件
function UIGrid:initUnrealObject()
    local class = self.gridData.class;
    local buildingInfo = BuildingBonusM.queryBuildingView(class);

    if not buildingInfo then
        error("获取不到建筑查看配置，元素类型：" .. class);
        return;
    end

    local function onClicked(sender, eventType)
        if eventType == ccui.TouchEventType.began then
            -- 更新建筑访问
            self:updateBuildingVisit();

            local elementDbase = DungeonM.queryElement(self.gridData.element, "dbase");
            if elementDbase ~= nil and type(elementDbase["click_tip"]) == 'string' then
                -- 配置了点击提示，直接弹提示
                alert(getLocStr(elementDbase["click_tip"]));
                return;
            end

            if elementDbase ~= nil and type(elementDbase["ok_click_tip"]) == "string" then
                local function okFunc()
                    alert(getLocStr(elementDbase["ok_click_tip"]));
                end
                -- 打开建筑查看界面
                self:openBuildingView(okFunc, 0, buildingInfo["dialog"]);
            else
                -- 打开建筑查看界面
                self:openBuildingView(nil, 0, buildingInfo["dialog"]);
            end

            -- 音效
            AudioM.playFx("ui_open1");
        end
    end

    -- 建筑图标
    local iconPath = getLevelItemIconPath(buildingInfo["icon"]);

    self.icon = ccui.ImageView:create(iconPath);
    self.icon:setTouchEnabled(true);
    self.icon:addTouchEventListener(onClicked);
    self.item:addChild(self.icon);
    self.iconPath = iconPath;

    -- 需要向上偏移的物件
    local dbase = DungeonM.query(class, "dbase");
    if type(dbase["position_y"]) == 'number' then
        self.item:setPositionY(dbase["position_y"]);
    end
    -- 呼吸
    if dbase["need_animation"] == 1 then
        self.needAnimation = true;
    end
end

-- 初始化幽灵船
function UIGrid:initGhostShip()
    self.item:removeAllChildren();
    local pos = self.gridData:getPos();

    if self.gridData.monster then
        -- 幽灵船还没被雇佣
        -- 创建幽灵船怪
        self:createShipMonster();
    elseif self.gridData.ship then
        -- 幽灵船已被雇佣
        -- 初始化幽灵船
        self:createShip();
        -- 气泡
        self:redrawShipSkill();
    else
        trace("UIGrid", "幽灵船对象不存在，无法初始化。");
    end
end

-- 创建幽灵船怪
function UIGrid:createShipMonster()
    local ship = self.gridData.monster;

    assert(ship, "幽灵船对象不存在");

    -- 注册模型点击事件
    self.isLongPress = false;

    -- 注册点击事件
    local function onClicked(sender, eventType)
        local function longPressCallback()
            self:showMonsterInfo();
        end

        -- 处理长按
        if handleLongPress(sender, eventType, longPressCallback) then
            return;
        end

        if eventType == ccui.TouchEventType.ended then
            local sequence = SkillM.getSequence(self.gridData.monster)
            if sequence ~= nil and (not sequence:empty()) then
                -- 上轮攻击未结束
                cclog("正在战斗中");
            end

            -- 检测英雄是否还活着
            if ME.user:isDead() then
                return false;
            end

            -- 幽灵船已死亡
            if self.gridData.monster:isDead() then
                -- 尝试雇佣
                self:tryEmployShip(self.gridData.monster);
                return false;
            end

            -- 需要判断是否能攻击
            if not self.gridData:canHit() then
                alert(getLocStr("cannot_reach"));
                return false;
            end

            -- 怪物攻击先在此处模拟
            DungeonActionM.go("physic_attack", self.gridData:getPos());
            EventMgr.fire(event.PLAYER_MOVE, self.index);
            return true;
        end
    end

    -- 设置点击范围
    local clickPanel = findChildByName(self.monsterBgNode, "click_panel");
    clickPanel:setVisible(true);
    clickPanel:addTouchEventListener(onClicked);

    local clickFrame = GhostShipM.query(ship.classId, "click_frame");
    clickPanel:setContentSize(clickFrame[1], clickFrame[2]);

    -- 隐藏边框
    local monsterFrame = findChildByName(self.monsterBgNode, "monster_frame");
    monsterFrame:setTouchEnabled(false);
    monsterFrame:setVisible(false);
    self.monsterBgNode:setVisible(true);

    local function showShipInfo()
        -- 先移除旧的外观
        if self.monsterNode.shipDisplay then
            self.monsterNode:removeChild(self.monsterNode.shipDisplay);
            self.monsterNode.shipDisplay = nil;
        end

        -- 可能被变形了
        if ship.dbase:query("changeClassId") then
            -- 创建新怪物模型
            local display = self:createExtraMonsterDisplay(ship.dbase:query("changeClassId"));
            -- 更换模型
            self.monsterNode.monsterDisplay = display;
            self.monsterNode:addChild(display);

            -- 创建幽灵船水流动的动画
            playEffect(self.monsterNode, 1218);

            if self:getIsPause() then
                -- 需要暂停动作
                display:setNeedPause(true);
                display:pauseActions();
            else
                display:setNeedPause(false);
            end
        else
            -- 创建幽灵船外观
            createShipModel(self.monsterNode, ship.classId);
        end

        self.monsterNode:setVisible(true);

        -- 显示攻击和生命
        self.barNode:setVisible(true);
        self:redrawShipAttackAndHp();

        -- 显示自动攻击轮次倒计时
        self:updateCountDown();
        -- 暂停动作
        self:pauseMonsterAction();
        -- 更换模型
        self:changeMonsterModel();
    end

    performWithDelay(self, showShipInfo, 0.01);

    -- 怪物攻击
    self.attack = self.gridData.attack;
    self.hasMonster = true;
end

-- 尝试雇佣幽灵船
function UIGrid:tryEmployShip(ship)
    -- 计算雇佣消耗
    local cost = GhostShipM.calcEmployShipCost(ship.classId);
    local attrib = table.keys(cost)[1];
    local value  = table.values(cost)[1];
    local name   = FieldsM.getFieldName(attrib);

    local function employ()
        -- 判断能否雇佣
        local pos = self.gridData:getPos()
        local ret = GhostShipM.canEmploy(pos);
        if ret ~= true then
            if ret == "cost_lack" then
                alert(string.format(getLocStr("something_not_enough"), name));
            else
                cclog("雇佣幽灵船失败，原因：" .. tostring(ret));
            end

            AudioM.playFx("button_receive_failed");
            return;
        end

        -- 雇佣
        DungeonActionM.go("employ_ship", pos);
    end

    AudioM.playFx("button_spell");

    -- 弹出雇佣确认框
    local msg = string.format(getLocStr("employ_ship_confirm"), value, name);
    confirm2(CONFIRM_TYPE_OK, getLocStr("ghost_ship"), msg,
        employ, nil, getLocStr("btn_text_employ"));
end

-- 创建幽灵船（已被雇佣）
function UIGrid:createShip()
    local ship = ME.user.ship;

    assert(ship, "幽灵船对象不存在");

    local function showShipInfo()
        -- 先移除旧的外观
        if self.monsterNode.shipDisplay then
            self.monsterNode:removeChild(self.monsterNode.shipDisplay);
            self.monsterNode.shipDisplay = nil;
        end

        -- 创建外观
        createShipModel(self.monsterNode, ship.classId);
        self.monsterNode:setVisible(true);
        self.isPause = false;

        -- 显示攻击和生命
        self.barNode:setVisible(true);
        self:redrawShipAttackAndHp();

        -- 显示自动攻击轮次倒计时
        self:updateCountDown();

        -- 注册点击事件
        local function onClicked(sender, eventType)
            if eventType == ccui.TouchEventType.ended then
                AudioM.playFx("workshop_open_ui");
                -- 打开幽灵船主界面
                UIMgr.getCurrentScene():removeFormByName("UIShipMain");
                local uiShipMain = UIShipMain.create(ship);
                UIMgr.getCurrentScene():addForm(uiShipMain);
            end
        end
        local clickPanel = findChildByName(self.monsterBgNode, "click_panel");
        clickPanel:setVisible(true);
        clickPanel:addTouchEventListener(onClicked);

        local clickFrame = GhostShipM.query(ship.classId, "click_frame");
        clickPanel:setContentSize(clickFrame[1], clickFrame[2]);

        -- 隐藏边框
        local monsterFrame = findChildByName(self.monsterBgNode, "monster_frame");
        monsterFrame:setTouchEnabled(false);
        monsterFrame:setVisible(false);
    end

    performWithDelay(self, showShipInfo, 0.01);

    self.attack = self.gridData.attack;
    self.hasMonster = true;

    -- 注册技能气泡点击事件
    local dialogBg = findChildByName(self.bineSkillNode, "bg");
    dialogBg:setTouchEnabled(true);
    local function onDialogBgClick(sender, eventType)
        if eventType == ccui.TouchEventType.began then
            AudioM.playFx("button_click");
            -- 技能冷却中
            local skillId = GhostShipM.getInitiativeSkill();
            local cd = GhostShipM.getRemainSkillCD(ship, skillId);

            cclog("幽灵船技能cd(%d)", cd);

            if cd > 0 then
                alert(getLocStr("skill_cd_tip"));
                return;
            end

            -- 打开幽灵船施法界面
            UIMgr.getCurrentScene():removeFormByName("UIShipSelectTarget");
            local uiForm = UIShipSelectTarget.create(skillId);
            UIMgr.getCurrentScene():addForm(uiForm);
        end
    end
    dialogBg:addTouchEventListener(onDialogBgClick);
end

-- 显示幽灵船的攻击和生命
function UIGrid:redrawShipAttackAndHp()
    local ship;
    local hasEmployed;
    if self.gridData.monster then
        ship = self.gridData.monster;
        hasEmployed = false;
    else
        ship = ME.user.ship;
        hasEmployed = true;
    end

    -- 如果死亡了，需要隐藏血条和攻击条
    if ship:isDead() then
        self.barNode:setVisible(false);
    else
        self.barNode:setVisible(true);
    end

    local hpLabel = self.hpLabel;
    local hpImg = findChildByName(self.barNode, "hp_node/hp_bar");

    if hasEmployed then
        -- 被雇佣后的幽灵船没有生命
        hpLabel:setVisible(false);
        hpImg:setVisible(false);
    else
        hpImg:setVisible(true);
        hpLabel:setVisible(true);
        local hp = ship:getHp();
        hpLabel:setString(tostring(hp));
        self:setBarStyle(hpLabel);
    end

    -- 策划需求：暂时不要显示攻击
    local attackNode = self.atkBarNode;
    attackNode:setVisible(false);

    --[[
    local attack = ship:getAttack();
    local attackLabel = self.attackLabel;
    attackLabel:setString(tostring(attack));
    self:setBarStyle(attackLabel);
    --]]

    -- 处理幽灵船属性增加的特效
    self:handleShipAttribEffect(ship);
end

-- 幽灵船死亡的回调
function UIGrid:onShipDie()
    -- 重绘攻击和生命
    self:redrawShipAttackAndHp();

    -- 如果被变形了，需要还原回来
    if self.gridData.monster.dbase:query("changeClassId") then
        self.gridData.monster.dbase:delete("changeClassId");
        self:showOriMonsterModel();
    end
    self.isPause = false;
    self:showNormalCd();
end

-- 玩家雇佣幽灵船的回调
function UIGrid:onEmployShip()
    -- 提示雇佣了幽灵船
    alert(getLocStr("employ_ship_succ"));
    AudioM.playFx("market_buy_success");

    -- 重新初始化格子
    self:initGhostShip();
end

-- 重绘幽灵船技能气泡
function UIGrid:redrawShipSkill()
    -- 如果幽灵船学习了主动技能，则显示气泡
    local skillId = GhostShipM.getInitiativeSkill();
    local ship = ME.user.ship;
    if ship:ownSkill(skillId) then
        -- 如果不满足施放条件，则隐藏气泡
        if true ~= GhostShipM.canUseSkill(skillId) then
            self.bineSkillNode:setVisible(false);
            return;
        end

        if self.bineSkillNode:isVisible() then
            -- 技能气泡已经显示，不处理
            return;
        end

        self.bineSkillNode:setVisible(true);

        -- 绘制技能图标
        local skillIconPath = getSkillIconPath(SkillM.query(skillId, "icon"));
        local skillIconImg = findChildByName(self.bineSkillNode, "icon");
        skillIconImg:loadTexture(skillIconPath);

        -- 播放气泡动画
        if not self.bineSkillNode.isPlaying then
            playBubbleAnima(self.bineSkillNode, false);
            self.bineSkillNode.isPlaying = true;
        end
    else
        self.bineSkillNode:setVisible(false);
    end
end

-- 处理幽灵船属性增加的特效
function UIGrid:handleShipAttribEffect(ship)
    local parentNode = UIDungeonMgr:getCurLevel():getEffectParent();
    local function delayHandle()
        local attack = ship:getAttack();
        local magic  = ship:getMagic();

        local pos = ship:getPos();
        local binePos = UIDungeonMgr:getCurLevel():getBinePos(pos);

        local addList = {};

        -- 攻击+N效果
        local delay = 0.01;
        local lastAttack = ship.dbase:queryTemp("last_attack");
        if lastAttack ~= nil then
            local addValue =  attack - lastAttack;
            if addValue > 0 then
                table.insert(addList, { "attack", addValue, cc.pAdd(binePos, cc.p(0, 30)) });
            end
        end
        ship.dbase:setTemp("last_attack", attack);

        -- 魔力+N效果
        local lastMagic = ship.dbase:queryTemp("last_magic");
        if lastMagic ~= nil then
            local addValue =  magic - lastMagic;
            if addValue > 0 then
                table.insert(addList, { "magic", addValue, cc.pAdd(binePos, cc.p(0, 30)) });
            end
        end
        ship.dbase:setTemp("last_magic", magic);

        -- 播放特效
        local i = 1;
        for _, arr in pairs(addList) do
            local attrib = arr[1];
            local value  = arr[2];
            local offset = arr[3];
            if value > 0 then
                local function callFunc()
                    playAttribUpEffect(parentNode, value, offset, attrib);
                end

                performWithDelay(self, callFunc, 0.01 + 0.8* (i - 1));
                i = i + 1;
            end
        end
    end

    performWithDelay(self, delayHandle, 0.01);
end

-- 更新建筑访问
function UIGrid:updateBuildingVisit()
    if not DungeonM.checkDungeonAction() then
        return;
    end

    local gridType = self.gridData.type;
    if not self.visitBuildingRecord[gridType] then
        -- 触发访问建筑的事件，只触发一次
        DungeonActionM.go("visit_building", self.gridData:getPos());
        self.visitBuildingRecord[gridType] = true;
    end
end

-- 角色延时发言
function UIGrid:doRoleDelayTalk(content, delay)
    if type(delay) ~= "number" or delay <= 0 then
        self:doRoleTalk(content);
    else
        performWithDelay(self, function() self:doRoleTalk(content); end, delay);
    end
end

-- 角色延时发言2
function UIGrid:doRoleDelayTalk2(content, delay, talkType)
    local function talk()
        if talkType == "special_monster" then
            self:doRoleTalk(content, 3);
        else
            self:doRoleTalk2(content);
        end
    end

    if type(delay) ~= "number" or delay <= 0 then
        talk();
    else
        self.wordsNode:stopAllActions();
        performWithDelay(self.wordsNode, talk, delay);
    end
end

-- 角色发言
function UIGrid:doRoleTalk(content, time, talkId)
    local pos = self.gridData:getPos();

    -- 可发言的格子类型
    local validTypes = {
        GRID_TYPE_MONSTER,    -- 怪物
        GRID_TYPE_BELIEVER,   -- 信徒
        GRID_TYPE_TREE_HOUSE, -- 树屋
        GRID_TYPE_GUARDS,     -- 侍卫
        GRID_TYPE_DISPLAY_OBJECT,    -- 显示物件
        GRID_TYPE_CG_UNREAL_OBJ,     -- 创世之门夹层角色
    };

    if table.indexOf(validTypes, self.gridData.type) == -1 then
        trace("UIGrid", "格子(%d)类型不符，不能发言。", pos);
        return;
    end

    -- 发言框存活时间
    local surviveTime = 0;
    talkId = talkId or MonsterM.getTalkId();

    if DungeonBossM.isInBossLayer() then
        if ActivityDungeonM.isInActivityBossLayer() then
            -- 活动副本冈布奥
            self.wordsNode:setPositionY(125);
            surviveTime = time or 5.0;
        else
            -- 一般BOSS层
            talkId = TalkTemplateM.getTalkId(GRID_TYPE_MONSTER, 2);
            surviveTime = 3.0;
        end
    elseif self.gridData.type == GRID_TYPE_BELIEVER then
        -- 信徒的发言
        talkId = TalkTemplateM.getTalkId(GRID_TYPE_BELIEVER, 1);
        self.wordsNode:setPositionY(80);
        surviveTime = 4.0;
    elseif self.gridData.type == GRID_TYPE_DISPLAY_OBJECT then
        -- 信徒的发言
        talkId = TalkTemplateM.getTalkId(self.gridData.type, 1);
        surviveTime = 4.0;
    else
        self.wordsNode:setPositionY(110);

        if self.gridData.monster and self.gridData.monster:isSlime() then
            -- 冈布奥怪
            surviveTime = 5.0;
        elseif self.gridData.type == GRID_TYPE_TREE_HOUSE then
            -- 树屋
            surviveTime = 3.5;
        else
            surviveTime = 3.0;
        end
    end

    -- 发言概率
    local rand = math.random(1000);
    local ratio = TalkTemplateM.query(talkId, "ratio");
    if rand > ratio then
        return;
    end

    -- 获取说话内容
    content = content or TalkTemplateM.query(talkId, "content");

    local pos = self.gridData:getPos();
    local frameType;
    if pos <= DUNGEON_WIDTH then
        -- 第一行需要特殊处理，避免发言框被遮挡
        frameType = WORDS_FRAME_ARROW_TOP_RIGHT;
        self.wordsNode:setPositionY(self.wordsNode:getPositionY() - 175);
    else
        frameType = WORDS_FRAME_ARROW_BOTTOM_RIGHT;
    end

    -- 播放发言特效
    local framePos = cc.pAdd(cc.p(self:getPosition()), cc.p(self.wordsNode:getPosition()));

    if self.gridData.monster then
        -- 位置偏移
        local classId = self.gridData.monster:query("class_id");
        local dbase = MonsterM.query(classId, "dbase");
        local offset = dbase["model_offset"];
        if type(offset) == "number" then
            framePos = cc.pAdd(cc.p(framePos), cc.p(0, offset));
        end
    end

    self.isTalking = true;
    playRoleTalk(pos, framePos, content, frameType, surviveTime);

    performWithDelay(self, function() self.isTalking = nil; end, surviveTime);

    -- 播放音效
    local audio = TalkTemplateM.query(talkId, "audio_id");
    if type(audio) == "string" and #audio > 0 then
        AudioM.playFx(audio);
    end
end

-- 角色发言（发言框是对称的且不带箭头）
function UIGrid:doRoleTalk2(content, surviveTime)
    if self.gridData.monster and self.gridData.monster:isDead() then
        -- 已经死亡，不处理
        return;
    end

    local pos = self.gridData:getPos();

    -- 发言框存活时间
    local surviveTime = surviveTime or 3.0;

    local pos = self.gridData:getPos();

    -- 播放发言特效
    local framePos = cc.pAdd(cc.p(self:getPosition()), cc.p(self.wordsNode:getPosition()));

    if self.gridData.monster then
        -- 位置偏移
        local classId = self.gridData.monster:query("class_id");
        local offset = NormalAudienceM.queryAudience(classId, "offset");
        if NormalAudienceM.needFixPosition() and offset then
            framePos = cc.pAdd(cc.p(framePos), cc.p(offset[1], offset[2]));
        end
    end

    self.isTalking = true;
    playRoleTalk2(pos, framePos, content, surviveTime);

    performWithDelay(self, function() self.isTalking = nil; end, surviveTime);
end

-- 停止发言
function UIGrid:stopTalking()
    self.isTalking = nil;
    local pos = self.gridData:getPos();
    trace("UIGrid", "格子(%d)停止发言。", pos);
    removeRoleTalk(pos);
end

-- 初始化祭坛
function UIGrid:initAltar()
    self.item:removeAllChildren();

    local class = self.gridData.class;
    local buildingInfo = BuildingBonusM.queryBuildingView(class);

    if not buildingInfo then
        error("获取不到建筑查看配置，元素类型：" .. class);
        return;
    end

    local function callFunc()
        local pos = self.gridData:getPos();
        DungeonAltarM.doEvent(pos);
    end

    local function onClicked(sender, eventType)
        if eventType == ccui.TouchEventType.began then
            if self.gridData.state == GRID_STATE_DISABLE then
                -- 已经失效，不处理
                return;
            end

            -- 更新建筑访问
            self:updateBuildingVisit();

            -- 打开建筑查看界面
            self:openBuildingView(callFunc);

            -- 音效
            AudioM.playFx("ui_open1");
        end
    end

    -- 建筑图标
    local iconPath = getLevelItemIconPath(buildingInfo["icon"]);

    self.icon = ccui.ImageView:create(iconPath);
    self.icon:setTouchEnabled(true);
    self.icon:addTouchEventListener(onClicked);
    self.item:addChild(self.icon);
    self.iconPath = iconPath;
end

-- 奇怪的祭坛激活的事件
function UIGrid:onOddAtarActive()
    onOddAtarActive(self);
end

-- 往妖精之井中投放金币的事件
function UIGrid:onThrowCoinToWell(bonus, optionIndex)
    onThrowCoinToWell(self, bonus, optionIndex);
end

-- 木桌
function UIGrid:initDesk()
    self:initCommonBuilding();
end

-- 妖精被放走的事件回调
function UIGrid:onRescuePixy()
    onRescuePixy(self);
end

-- 进入夹层迷宫的回调
function UIGrid:onEnterUnreal()
    -- 移除格子
    self.item:removeAllChildren();
end

-- 水晶球
function UIGrid:initCrystalBall()
    self:initCommonBuilding();
end

-- 初始化老式电视机
function UIGrid:initOldTv()
    self.item:removeAllChildren();

    local class = self.gridData.class;
    local buildingInfo = BuildingBonusM.queryBuildingView(class);

    if not buildingInfo then
        error("获取不到建筑查看配置，元素类型：" .. class);
        return;
    end

    local function onClicked(sender, eventType)
        if eventType == ccui.TouchEventType.began then
            if self.gridData.state == GRID_STATE_DISABLE then
                -- 已经失效，不处理
                return;
            end

            -- 更新建筑访问
            self:updateBuildingVisit();

            -- 音效
            AudioM.playFx("ui_open1");

            -- 打开电视机界面
            UIMgr.getCurrentScene():removeFormByName("UIOldTv");
            local uiForm = UIOldTv.create(self.gridData);
            UIMgr.getCurrentScene():addForm(uiForm);
        end
    end

    -- 建筑图标
    local iconPath = getLevelItemIconPath(buildingInfo["icon"]);

    self.icon = ccui.ImageView:create(iconPath);
    self.icon:setTouchEnabled(true);
    self.icon:addTouchEventListener(onClicked);
    self.item:addChild(self.icon);
    self.iconPath = iconPath;

    -- 查询一下广告是否就绪
    queryAdReadyState();
end

-- 关注关闭广告屏幕的事件
function UIGrid:onDismissAdScreen()
    --self.item:removeAllChildren();
end

-- 绘制能量条
function UIGrid:updateEnergy()
    local energyNode    = self.energy;
    local monster       = self.gridData.monster;
    if not monster then
        energyNode:setVisible(false);
        return;
    end

    if not self.gridData:isOpened() then
        energyNode:setVisible(false);
        return;
    end

    local classId = monster.classId;
    local dbase = MonsterM.query(classId, "dbase");
    local maxEnergy = dbase["max_energy"] or 0;

    if monster.dbase:query("max_energy") then
        maxEnergy = monster.dbase:query("max_energy");
    end

    if not maxEnergy or maxEnergy <= 0 then
        energyNode:setVisible(false);
        return;
    end

    local curEnergy     = monster.dbase:query("energy") or 0;
    local curEnergyIcon = findChildByName(self.node, "energy/cur_energy");
    local textLable     = findChildByName(self.node, "energy/text");

    energyNode:setVisible(true);
    curEnergyIcon:setScaleX(curEnergy / maxEnergy * 1.1);

    if textLable:getString() == "" then
        TextStyleM.setTextStyle(textLable, TextStyleM.TEXT_SIZE_SMALL, TextStyleM.TEXT_COLOR_DARK_BROWN);
    end

    textLable:setString(curEnergy .. " / " .. maxEnergy);
end

-- 修正能量条位置
function UIGrid:fixEnergyPos()
    local energyNode    = self.energy;
    local textLabel     = findChildByName(self.node, "energy/text");

    -- 调整位置
    local gridWidth  = 0;
    local gridHeight = 70;
    energyNode:setPosition(energyNode:getPositionX() - gridWidth, energyNode:getPositionY() - gridHeight);

    energyNode:setScaleX(3.0);
    energyNode:setScaleY(2.0);
    textLabel:setScaleX(0.33);
    textLabel:setScaleY(0.5);
end

-- 播放能量条增加效果
function UIGrid:playCollectEnergy(amount)
    playGridEnergyEffect(self);
end

-- 播放能量条减少效果
function UIGrid:playCostEnergy(amount)
    playGridEnergyEffect(self);
end

-- 排行榜建筑消失
function UIGrid:onToplistBuildingDispear(bonus)
    -- 播放建筑奖励特效
    buildingBonusEffect(self);

    self:playBonusListEffect(bonus);

    local function showTip()
        alert(getLocStr("gain_ranklist_bonus"));
    end

    performWithDelay(self, showTip, 2);
end

-- 显示怪物的动作发言
function UIGrid:showAnimationTalk(actionType, skillId)
    local talkInfo;
    local classId;
    if self.gridData.monster then
        classId = self.gridData.monster.classId;
        talkInfo = AnimationTalkM.getActionSkillTalk(classId, actionType, skillId);
    end

    if not talkInfo or #table.keys(talkInfo) <= 0 then
        return;
    end

    if not self.gridData.monster:isBoss() then
        -- 普通怪发言
        local contentList = talkInfo.content;
        if type(contentList) ~= "table" or #contentList == 0 then
            return;
        end

        local index = math.random(#contentList);
        local content = contentList[index];
        self:doRoleTalk(content);
        return;
    end

    local talkNode = findChildByName(self.node, "boss_talk_node");
    local contentLabel = findChildByName(self.node, "boss_talk_node/content");

    -- 判断优先级
    if (talkNode.priority or 0) > talkInfo.priority then
        return;
    end

    -- 初始化发言随机数
    DungeonBossM.setTalkRandom();

    if not DungeonBossM.canBossTalk(talkInfo.ratio) then
        return;
    end

    -- 随机抽取一个发言
    local contentList = talkInfo.content;
    local index = math.random(#contentList);
    local content = contentList[index];

    if content and type(content) == "string" then
        TextStyleM.setTextStyle(contentLabel, TextStyleM.TEXT_SIZE_SMALL, TextStyleM.TEXT_COLOR_DARK_BROWN);
        contentLabel:setString(content);

        -- 偏移下坐标
        if not talkNode.initX then
            talkNode.initX = talkNode:getPositionX();
            talkNode.initY = talkNode:getPositionY();
        end

        local offsetX = 0;
        local offsetY = 0;

        if type(talkInfo.offset_x) == "number" then
            talkNode:setPositionX(talkNode.initX + talkInfo.offset_x);
        end
        if type(talkInfo.offset_y) == "number" then
            talkNode:setPositionY(talkNode.initY + talkInfo.offset_y);
        end

        local contentBg = findChildByName(self.node, "boss_talk_node/bg");
        autoResizeBg(contentLabel, contentBg, 65, 65);

        talkNode.priority = talkInfo.priority;
        talkNode:setOpacity(255);
        talkNode:setVisible(true);
        talkNode:stopAllActions();
        talkNode:runAction(cc.Sequence:create(cc.DelayTime:create(1), cc.FadeOut:create(1.5)));

        performWithDelay(talkNode, function ( )
            talkNode:setVisible(false);
            talkNode.priority = 0;
        end, 2.5)
    end
end

-- 德古拉的展柜解锁
function UIGrid:onShowcaseUnlock()
    onShowcaseUnlock(self);
end

-- 打开商店
function UIGrid:openShop()
    -- 获取格子位置
    local pos = self.gridData:getPos();

    -- 获取商店类型
    local shopType = self.gridData.shopType;

    if not shopType then
        trace("UIGrid", "无法打开商店：商店类型非法。");
        return;
    end

    -- 音效
    AudioM.playFx("dup_shop");

    -- 打开商店
    if UIMgr.getCurrentScene():isOpen("UIDungeonShop") then
        UIMgr.getCurrentScene():removeFormByName("UIDungeonShop");
    end
    local uiDungeonShop = UIDungeonShop.create(pos, shopType);
    UIMgr.getCurrentScene():addForm(uiDungeonShop);

    local props  = PropM.fetchProps(ME.user, "shop");
    if shopType == "spell" or shopType == "treasure" then
        -- 交易大师特技
        if #props > 0 then
            -- 触发特技发动事件
            EventMgr.fire(event.SPECIAL_SKILL_TRIGGER, { propId = 514 });
        end
    end
end

-- 领取圣棺奖励的回调
function UIGrid:onTakeCoffinBonus(bonus)
    onTakeCoffinBonus(self, bonus);
end

-- 播放技能失效特效
function UIGrid:playInvalidSkillEffect(skillId)
    local monster = self.gridData.monster;

    -- 梦魇巨龙的梦魇护盾失效了
    if skillId == 2095 and monster.classId == 511 then
        playEffect(self, 1524, -80, 200);
    end
end

-- 执行怪物出场发言
function UIGrid:doMonsterAppearTalk()
    if not self.gridData.monster or self.gridData.monster:isDead() then
        return;
    end

    local classId = self.gridData.monster.classId;

    if self.gridData.monster.isSlime and self.gridData.monster:isSlime() then
        -- 冈布奥怪角斗台中发言
        if ArenaM.isInArena() then
            -- 冈布奥怪角斗台中发言
            local function talk()
                local content = ArenaM.querySlime(classId, "appear_dialog");
                self:doRoleTalk(content);
            end
            performWithDelay(self, talk, 1.0);
        end
    elseif self.gridData.monster.isBoss and self.gridData.monster:isBoss() then
        if ActivityDungeonM.isInActivityBossLayer() then
            -- 活动副本BOSS层
            local function talk()
                local content = ActivityBossM.queryBoss(classId, "appear_dialog");
                if type(content) == "string" and #content > 0 then
                    self:doRoleTalk(content);
                end
            end
            performWithDelay(self, talk, 1.0);
        end
    end
end

-- 树屋攻击发言
function UIGrid:treeHouseTalk(skillId, args)
    local pos = self.gridData:getPos();
    if self.gridData.type ~= GRID_TYPE_TREE_HOUSE then
        -- 不是树屋，不处理
        trace("UIGrid", "格子(%d)不是怪物或信徒或树屋，不能发言。", pos);
        return;
    end

    -- 根据skillId获取发言内容
    local content = TreeHouseM.getAttackTalk(skillId, args);
    local function talk()
        self:doRoleTalk(content);
    end

    performWithDelay(self, talk, 0.1);
end

-- 玩家穿戴装备的回调
function UIGrid:onEquip(classId)
    if not self.icon then
        return;
    end

    -- 如果是特殊视力道具
    if DungeonEggM.isSpecialVisionItem(classId) then
        -- 如果是阴影空间格子，需要重绘一下
        if self.gridData.type == GRID_TYPE_SHADOW_SPACE then
            self.icon:loadTexture(getLevelItemIconPath("shadow_statue"));
        end
    end

    if self.gridData.type == GRID_TYPE_BRAVE_CERTIFICATE then
        -- 勇者之证格子
        local elementDbase = DungeonM.queryElement(self.gridData.element, "dbase");
        if not BraveVSDragonM.hasGotBuildingBonus(self.gridData.class) and
            type(elementDbase["require_equip"]) == 'number' then
            local classId = elementDbase["require_equip"];
            if EquipM.isEquiped(classId) then
                self.icon:setVisible(true);
            end
        end
    end
end

-- 玩家卸下装备的回调
function UIGrid:onUnEquip(classId)
    if not self.icon then
        return;
    end

    -- 如果是特殊视力道具
    if DungeonEggM.isSpecialVisionItem(classId) then
        -- 如果是阴影空间格子，需要重绘一下
        if self.gridData.type == GRID_TYPE_SHADOW_SPACE then
            if DungeonEggM.hasEquipedVisionItem() then
                self.icon:loadTexture(getLevelItemIconPath("shadow_statue"));
            else
                self.icon:loadTexture(getLevelItemIconPath("shadow_statue2"));
            end
        end
    end

    if self.gridData.type == GRID_TYPE_BRAVE_CERTIFICATE then
        -- 勇者之证格子
        local elementDbase = DungeonM.queryElement(self.gridData.element, "dbase");
        if type(elementDbase["require_equip"]) == 'number' then
            local classId = elementDbase["require_equip"];
            if not EquipM.isEquiped(classId) then
                self.icon:setVisible(false);
            end
        end
    end
end

-- 东方神龛提交事件回调
function UIGrid:onEastShrineSubmit(classId, bonus, isSucc)
    onEastShrineSubmit(self, classId, bonus, isSucc);
end

-- 机械祭坛提交事件回调
function UIGrid:onMachineAltarSubmit(classId, bonus, isSucc)
    onMachineAltarSubmit(self, classId, bonus, isSucc);
end

-- 通用提交建筑提交事件回调
function UIGrid:onNormalSubmit(classId, bonus, isSucc)
    onNormalSubmit(self, classId, bonus, isSucc);
end

-- 乌龟大师提交事件回调
function UIGrid:onMasterOogwaySubmit(classId, bonus, isSucc)
    onMasterOogwaySubmit(self, classId, bonus, isSucc);
end

-- 树屋激活
function UIGrid:onTreeHouseActive()
    monkeyEffect(self);
end

-- 树屋猴子攻击
function UIGrid:onMoneyAttack(skillId)
    return monkeyAttackEffect(self, skillId);
end

function UIGrid:initJustice()
    self:initCommonBuilding();
end

-- 怪物转化为建筑的回调
function UIGrid:onMonsterTransBuilding()
    local grid = self.gridData;
    if grid.type ~= GRID_TYPE_MONSTER or
        grid.state ~= GRID_STATE_DISABLE then
        return;
    end

    -- 销毁怪物模型
    self.hasMonster = false;
    self:destroyMonsterDisplay();

    -- 隐藏怪物背景图和血条
    self.monsterBgNode:setVisible(false);
    self.barNode:setVisible(false);
    local cdNode = findChildByName(self.barNode, "cd_node");
    cdNode:setVisible(false);

    -- 重置暂停标记
    self.isPause = false;

    local function playUnlockEffect()
        playEffect(self, 1261, -18, 140, nil, 1.3);
        playEffect(self, 1262, -20, 120, nil, 1.3);
    end
    performWithDelay(self, playUnlockEffect, 0.5);
end

-- 怪物转化为建筑的回调
function UIGrid:onBuildingTransBuilding(effectId)
    -- 配置有光效，播放配置的光效
    if effectId then
        playEffect(self, effectId);

        local function playUnlockEffect()
            playEffect(self, 1261, -18, 140, nil, 1.3);
            playEffect(self, 1262, -20, 120, nil, 1.3);
        end
        performWithDelay(self, playUnlockEffect, 0.5);
    else
        -- 默认的建筑物摧毁效果
        playBuildingDisappearEffect(self, "bomb");
    end
end

-- 激活水晶结界事件
function UIGrid:onCrystalActive()
    onCrystalActive(self);
end

-- 清除召唤兽模型
function UIGrid:onSummonTransBuilding()
    local grid = self.gridData;

    if grid.type ~= GRID_TYPE_SUMMON then
        return;
    end

    -- 销毁怪物模型
    self.hasMonster = false;
    self:destroySummonDisplay();

    -- 隐藏怪物背景图和血条
    self.monsterBgNode:setVisible(false);
    self.barNode:setVisible(false);
    local cdNode = findChildByName(self.barNode, "cd_node");
    cdNode:setVisible(false);

    -- 重置暂停标记
    self.isPause = false;

    -- 重置上次血量和缓存数据
    self.hpLabel.lastHpColor = nil;
    self.attackLabel.lastAttackColor = nil;
    self.hpLabel.lastHpVal = nil;
    self.attackLabel.lastAttackVal = nil;
end

-- 播放建筑奖励
function UIGrid:onShowBuildingBonus(bonus, class)
    onShowBuildingBonus(self, bonus, class);
end

-- 侍卫发言
function UIGrid:doGuardsTalk()
    doGuardsTalk(self);
end

-- 提交小徽章的回调
function UIGrid:onSubmitSmallBonus(bonus)
    onSubmitSmallBonus(self, bonus);
end

-- 救出公主后国王发言
function UIGrid:doKingTalkRescue()
    doKingTalkRescue(self);
end

-- 公主出现的回调
function UIGrid:onPrincessAppeared()
    onPrincessAppeared(self);
end

-- 勇者斗恶龙通用建筑奖励回调
function UIGrid:onBraveBuildingBonus(bonus, isDisappear)
    local class = self.gridData.class;
    local buildingInfo = BuildingBonusM.queryBuildingView(class) or {};

    local tip = buildingInfo["bonus_tip"]
    if type(tip) == 'string' and string.len(tip) > 0 then
        -- TODO 如果是侍卫队长，首次获得奖励时的提示需要特殊处理一下
        if self.gridData.type == GRID_TYPE_GUARDS_LEADER and
           BraveVSDragonM.getGuardsLeaderBonusTimes() == 1 then
            tip = getLocStr("guards_leader_bonus_tip");
        elseif self.gridData.type == GRID_TYPE_HOLY_SHRINE then
            -- 神圣祠堂
            self:onHolyShrineSubmited(bonus);
            return;
        end

        local alertList = splitString(tip, "\n");
        splitAlert(alertList);
    end

    self:playBonusListEffect(bonus, isDisappear);
end

-- 旅者帐篷事件的回调
function UIGrid:onTravellerTentEvent(hp, mp)
    onTravellerTentEvent(self, hp, mp);
end

-- 埋葬巨坑
function UIGrid:buryHugeHole()
    buryHugeHole(self);
end

-- 神圣祠堂提交的回调
function UIGrid:onHolyShrineSubmited(bonus)
    onHolyShrineSubmited(self, bonus);
end

-- 关注元素转换
function UIGrid:onTransEliminate()
    onTransEliminate(self);
end

-- 暗之石柱解除
function UIGrid:onStoneDisable()
    onStoneDisable(self);
end

-- 播放商店解锁特效
function UIGrid:playShopUnlockEffect()
    playShopUnlockEffect(self);
end

-- 开启boss层石柱
function UIGrid:onOpenBossStone()
    onOpenBossStone(self);
end

-- 关闭boss层石柱
function UIGrid:onCloseBossStone()
    onCloseBossStone(self);
end

-- boss层石柱清除
function UIGrid:onStoneDisappear()
    onStoneDisappear(self);
end

-- 治疗效果
function UIGrid:bishopCureEffect(addList)
    bishopCureEffect(self, addList);
end

-- 阿瓦隆巨炮被摧毁
function UIGrid:cannonDestory()
    performWithDelay(self, function()
        playCannonDestoryEffect(self);
    end, 1);
end

-- 向阿瓦隆实验室提交物品
function UIGrid:submitToAvalonLab(args)
    onSubmitToLab(self, args);
end

-- 拆除定时炸弹
function UIGrid:removeTimeBomb(args)
    onRemoveTimeBomb(self, args);
end

-- 摧毁毒气装置
function UIGrid:destoryGasDevice(args)
    onDestoryGasDevice(self, args);
end

-- 摧毁钢铁之门
function UIGrid:destorySteelDoor(args)
    onDestorySteelDoor(self, args);
end

-- 钢铁之门转换废墟
function UIGrid:steelDoorTrans(args)
    onSteelDoorTrans(self, args);
end

-- 更新定时炸弹剩余时间
function UIGrid:updateTimeBombTime()
    onUpdateTimeBombTime(self);
end

-- 重绘技能气泡
function UIGrid:redrawSummonSkill()
    local pos = self.gridData:getPos();
    local grid = DungeonM.getGridByPos(pos);
    local summon = grid.summon;

    if not summon or summon:isDead() then
        -- 召唤兽不存在/死亡
        self.bineSkillNode:setVisible(false);
        return;
    end

    local skills = SummonM.getAllSkills(summon) or {};
    if #skills <= 0 then
        -- 没有主动技
        self.bineSkillNode:setVisible(false);
        return;
    end

    -- 判断倒计时
    local skillId = skills[1];
    local cd = SummonM.calcSkillCd(summon, skillId);
    if not cd or cd > 0 then
        self.bineSkillNode:setVisible(false);
        return;
    end

    -- 显示气泡
    self.bineSkillNode:setVisible(true);
    local skillIconPath = getSkillIconPath(SkillM.query(skillId, "icon"));
    local skillIconImg = findChildByName(self.bineSkillNode, "icon");
    skillIconImg:loadTexture(skillIconPath);
    self.bineSkillNode:setPositionY(65);
    skillIconImg:setPositionY(0);

    -- 判断消耗
    if not SummonM.isMpEnough(summon, skillId) then
        -- 魔法值不足，置灰
        setGrayTransMode(skillIconImg, true);
    else
        setGrayTransMode(skillIconImg, false);
    end

    -- 播放气泡动画
    if not self.bineSkillNode.isPlaying then
        playBubbleAnima(self.bineSkillNode, false);
        self.bineSkillNode.isPlaying = true;
    end
end

-- 需要显示建筑信息的观众
function UIGrid:audienceBuildingView()
    if not self.gridData.monster or
        self.gridData.monster:isDead() then
        return;
    end

    local classId = self.gridData.monster.classId;
    local dbase = NormalAudienceM.queryAudience(classId, "dbase") or {};
    local buildingView = dbase["building_view"];
    if not buildingView then
        return;
    end

    if buildingView == "energy_platform" then
        -- 超导充能平台
        local buildingInfo = BuildingBonusM.queryBuildingView(2025);
        local title = buildingInfo["title"];
        local iconPath = getLevelItemIconPath(buildingInfo["icon"]);
        local viewText = buildingInfo["view_text"];
        local dialog = buildingInfo["dialog"];
        local leftCd = self:getCd();
        dialog = string.format(dialog, leftCd);

        self:openBuildingView(nil, 0, dialog, viewText, title, iconPath);
    elseif buildingView == "interfere_device" then
        -- 电磁干扰仪
        local title = MonsterM.query(classId, "name");
        local iconPath = getLevelItemIconPath(NormalAudienceM.queryAudience(classId, "icon"));
        local viewText = getLocStr("btn_text_leave");
        local dialog = getLocStr("interfere_device_dialog");
        self:openBuildingView(nil, -20, dialog, viewText, title, iconPath,
            nil, nil, {["effectId"] = 1338, ["effectIdOffset"] = {65, 125, 1.1}});
    end
end

-- 灯塔奖励
function UIGrid:takeLighthouse()
    if type(redrawLighthouse) == "function" then
        redrawLighthouse(self);
    end

    -- 光效？
    -- 播放制作特效
    playEffect(self, self.gridData.effect_id, -18, 140, nil, 1.3);
end

-- 特殊入场形式
function UIGrid:specialEnterEffect(delay)
    if type(delay) ~= "number" then
        delay = 0;
    end

    -- 乱入怪出场光效
    if self.gridData.type == GRID_TYPE_MONSTER and self.gridData.monster and
        not self.gridData.monster:isDead() and
        (DungeonM.query(self.gridData.class, "rule") == "intrude_monster" or
            DungeonM.query(self.gridData.class, "rule") == "intrude_monster2") then
        -- "出现！鼹鼠矿工！"
        performWithDelay(self, function()
            alert(string.format(getLocStr("intrude_monster_appear"), MonsterM.query(self.gridData.monster.classId, "name")));
        end, delay);

        performWithDelay(self, function ()
            playMonsterAppearShadow(self, 1);
        end, 0.2 + delay);

        performWithDelay(self, function ()
            playIntrudeMonsterAppearShadow(self);
        end, 0.4 + delay);

        performWithDelay(self, function ()
            self:showIntrudeMonster();
        end, 1 + delay);

        -- 乱入怪出现音效
        performWithDelay(self, function ()
            local classId = self.gridData.monster.classId;
            local audioId = MonsterM.query(classId, "app_audio");
            if audioId and audioId ~= "" then
                AudioM.playFx(audioId);
            end
        end, 0.4 + delay);
    end

    -- 金蛋入场
    if self.gridData.type == GRID_TYPE_MINE_HILL
        and MineHillM.query(self.gridData.mine_id, "type") == MINE_TYPE_EGG then
        -- "出现！金蛋！"
        performWithDelay(self, function()
            alert(string.format(getLocStr("intrude_monster_appear"), MineHillM.query(self.gridData.mine_id, "name")));
        end, delay);

        performWithDelay(self, function()
            playMonsterAppearShadow(self, 1);
        end, 0.2 + delay);

        performWithDelay(self, function()
            playIntrudeMonsterAppearShadow(self);
        end, 0.4 + delay);

        performWithDelay(self, function()
            local scaleUp = cc.ScaleTo:create(0.4, 1);
            self.icon:setVisible(true);
            self.icon:setScale(0);
            self.icon:runAction(scaleUp);
            -- 金蛋不需要显示血量
            self.barNode:setVisible(false);
        end, 0.2 + delay);
    end

    return true;
end
